diff --git a/desktop/src/__tests__/memorySettings.test.tsx b/desktop/src/__tests__/memorySettings.test.tsx index 85234494..eb544f27 100644 --- a/desktop/src/__tests__/memorySettings.test.tsx +++ b/desktop/src/__tests__/memorySettings.test.tsx @@ -8,38 +8,19 @@ import { useSessionStore } from '../stores/sessionStore' import { useSettingsStore } from '../stores/settingsStore' import { useUIStore } from '../stores/uiStore' -const { memoryApiMock, openTargetStoreMock } = vi.hoisted(() => ({ +const { memoryApiMock } = vi.hoisted(() => ({ memoryApiMock: { listProjects: vi.fn(), listFiles: vi.fn(), readFile: vi.fn(), saveFile: vi.fn(), }, - openTargetStoreMock: { - targets: [{ id: 'finder', kind: 'file_manager', label: 'Finder', icon: 'finder', platform: 'darwin' }], - platform: 'darwin', - primaryTargetId: 'finder', - lastSuccessfulTargetId: null, - loading: false, - error: null, - fetchedAt: 1, - ensureTargets: vi.fn(), - refreshTargets: vi.fn(), - openTarget: vi.fn(), - }, })) vi.mock('../api/memory', () => ({ memoryApi: memoryApiMock, })) -vi.mock('../stores/openTargetStore', () => ({ - useOpenTargetStore: Object.assign( - (selector: (state: typeof openTargetStoreMock) => unknown) => selector(openTargetStoreMock), - { getState: () => openTargetStoreMock }, - ), -})) - vi.mock('../components/markdown/MarkdownRenderer', () => ({ MarkdownRenderer: ({ content }: { content: string }) => (
{content}
@@ -49,11 +30,6 @@ vi.mock('../components/markdown/MarkdownRenderer', () => ({ describe('MemorySettings', () => { beforeEach(() => { vi.clearAllMocks() - window.localStorage.removeItem('cc-haha-memory-pinned-projects') - openTargetStoreMock.targets = [{ id: 'finder', kind: 'file_manager', label: 'Finder', icon: 'finder', platform: 'darwin' }] - openTargetStoreMock.primaryTargetId = 'finder' - openTargetStoreMock.ensureTargets.mockResolvedValue(undefined) - openTargetStoreMock.openTarget.mockResolvedValue(undefined) useSettingsStore.setState({ locale: 'en' }) useSessionStore.setState({ sessions: [ @@ -201,56 +177,6 @@ describe('MemorySettings', () => { }) }) - it('pins projects above the regular project list from the project menu', async () => { - memoryApiMock.listProjects.mockResolvedValue({ - projects: [ - { - id: '-workspace-alpha', - label: '/workspace/alpha', - memoryDir: '/tmp/claude/projects/-workspace-alpha/memory', - exists: true, - fileCount: 1, - isCurrent: true, - }, - { - id: '-workspace-beta', - label: '/workspace/beta', - memoryDir: '/tmp/claude/projects/-workspace-beta/memory', - exists: true, - fileCount: 2, - isCurrent: false, - }, - ], - }) - - render() - - expect(await screen.findAllByText('workspace/alpha')).not.toHaveLength(0) - expect(await screen.findByText('workspace/beta')).toBeInTheDocument() - - fireEvent.click(screen.getByRole('button', { name: 'Project actions for workspace/beta' })) - fireEvent.click(await screen.findByRole('menuitem', { name: 'Pin project' })) - - expect(screen.getByText('Pinned projects')).toBeInTheDocument() - const projectRows = screen.getAllByTestId('memory-project-row') - expect(projectRows[0]).toHaveTextContent('workspace/beta') - expect(window.localStorage.getItem('cc-haha-memory-pinned-projects')).toContain('-workspace-beta') - }) - - it('opens a memory project in Finder through the existing open target flow', async () => { - render() - - expect(await screen.findAllByText('workspace/demo')).not.toHaveLength(0) - - fireEvent.click(screen.getByRole('button', { name: 'Project actions for workspace/demo' })) - fireEvent.click(await screen.findByRole('menuitem', { name: 'Open in Finder' })) - - await waitFor(() => { - expect(openTargetStoreMock.ensureTargets).toHaveBeenCalled() - expect(openTargetStoreMock.openTarget).toHaveBeenCalledWith('finder', '/workspace/demo') - }) - }) - it('renders nested memory files as a collapsible resource tree', async () => { memoryApiMock.listFiles.mockResolvedValue({ files: [ diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index 65419679..d44bee6b 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -556,7 +556,6 @@ export const en = { 'settings.memory.refresh': 'Refresh', 'settings.memory.projects': 'Projects', 'settings.memory.files': 'Memory files', - 'settings.memory.pinnedProjects': 'Pinned projects', 'settings.memory.resourceManager': 'Resource manager', 'settings.memory.editor': 'Editor', 'settings.memory.preview': 'Preview', @@ -580,12 +579,6 @@ export const en = { 'settings.memory.noFileMatches': 'No memory files match this search.', 'settings.memory.clearSearch': 'Clear search', 'settings.memory.toggleFolder': 'Toggle {name}', - 'settings.memory.projectActions': 'Project actions for {name}', - 'settings.memory.pinProject': 'Pin project', - 'settings.memory.unpinProject': 'Unpin project', - 'settings.memory.openInFinder': 'Open in Finder', - 'settings.memory.openInFinderFailed': 'Could not open the project in Finder.', - 'settings.memory.openInFinderUnavailable': 'No file manager is available.', // Settings > Plugins 'settings.plugins.title': 'Installed Plugins', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 0033a8be..0ec2d754 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -558,7 +558,6 @@ export const zh: Record = { 'settings.memory.refresh': '刷新', 'settings.memory.projects': '项目', 'settings.memory.files': '记忆文件', - 'settings.memory.pinnedProjects': '置顶项目', 'settings.memory.resourceManager': '资源管理器', 'settings.memory.editor': '编辑', 'settings.memory.preview': '预览', @@ -582,12 +581,6 @@ export const zh: Record = { 'settings.memory.noFileMatches': '没有匹配的记忆文件。', 'settings.memory.clearSearch': '清空搜索', 'settings.memory.toggleFolder': '展开或折叠 {name}', - 'settings.memory.projectActions': '{name} 的项目操作', - 'settings.memory.pinProject': '置顶项目', - 'settings.memory.unpinProject': '取消置顶', - 'settings.memory.openInFinder': '在“访达”中打开', - 'settings.memory.openInFinderFailed': '无法在访达中打开项目。', - 'settings.memory.openInFinderUnavailable': '没有可用的文件管理器。', // Settings > Plugins 'settings.plugins.title': '已安装插件', diff --git a/desktop/src/pages/MemorySettings.tsx b/desktop/src/pages/MemorySettings.tsx index 17d0295c..7107f8ac 100644 --- a/desktop/src/pages/MemorySettings.tsx +++ b/desktop/src/pages/MemorySettings.tsx @@ -1,18 +1,16 @@ import { useEffect, useMemo, useState } from 'react' -import type { MouseEvent, ReactNode } from 'react' -import { BookOpenText, ChevronDown, ChevronRight, Database, FileText, Folder, FolderGit2, FolderOpen, MoreHorizontal, Pin, PinOff, RefreshCw, RotateCcw, Save, Search, X } from 'lucide-react' +import type { ReactNode } from 'react' +import { BookOpenText, ChevronDown, ChevronRight, Database, FileText, Folder, FolderGit2, RefreshCw, RotateCcw, Save, Search, X } from 'lucide-react' import { Button } from '../components/shared/Button' import { MarkdownRenderer } from '../components/markdown/MarkdownRenderer' import { useTranslation } from '../i18n' import { formatBytes } from '../lib/formatBytes' import { useMemoryStore } from '../stores/memoryStore' -import { useOpenTargetStore } from '../stores/openTargetStore' import { useSessionStore } from '../stores/sessionStore' import { useUIStore } from '../stores/uiStore' import type { MemoryFile, MemoryProject } from '../types/memory' const DEFAULT_MEMORY_PATH = 'MEMORY.md' -const PINNED_MEMORY_PROJECTS_STORAGE_KEY = 'cc-haha-memory-pinned-projects' export function MemorySettings() { const t = useTranslation() @@ -37,14 +35,11 @@ export function MemorySettings() { } = useMemoryStore() const sessions = useSessionStore((s) => s.sessions) const activeSessionId = useSessionStore((s) => s.activeSessionId) - const addToast = useUIStore((s) => s.addToast) const pendingMemoryPath = useUIStore((s) => s.pendingMemoryPath) const setPendingMemoryPath = useUIStore((s) => s.setPendingMemoryPath) const [resourceQuery, setResourceQuery] = useState('') const [expandedProjectId, setExpandedProjectId] = useState(null) const [collapsedFolders, setCollapsedFolders] = useState>(new Set()) - const [pinnedProjectIds, setPinnedProjectIds] = useState>(() => readPinnedMemoryProjects()) - const [projectMenu, setProjectMenu] = useState<{ projectId: string; x: number; y: number } | null>(null) const activeSession = useMemo( () => sessions.find((session) => session.id === activeSessionId), @@ -57,18 +52,6 @@ export function MemorySettings() { () => filterProjects(projects, resourceQuery, selectedProjectId, files), [files, projects, resourceQuery, selectedProjectId], ) - const orderedProjects = useMemo( - () => orderMemoryProjects(filteredProjects, pinnedProjectIds), - [filteredProjects, pinnedProjectIds], - ) - const pinnedProjects = useMemo( - () => orderedProjects.filter((project) => pinnedProjectIds.has(project.id)), - [orderedProjects, pinnedProjectIds], - ) - const regularProjects = useMemo( - () => orderedProjects.filter((project) => !pinnedProjectIds.has(project.id)), - [orderedProjects, pinnedProjectIds], - ) const filteredFiles = useMemo( () => filterFiles(files, resourceQuery), [files, resourceQuery], @@ -93,20 +76,6 @@ export function MemorySettings() { setExpandedProjectId(selectedProjectId) }, [selectedProjectId]) - useEffect(() => { - if (!projectMenu) return - const close = () => setProjectMenu(null) - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') setProjectMenu(null) - } - document.addEventListener('click', close) - document.addEventListener('keydown', handleKeyDown) - return () => { - document.removeEventListener('click', close) - document.removeEventListener('keydown', handleKeyDown) - } - }, [projectMenu]) - useEffect(() => { if (!selectedProjectId || selectedFile || isLoadingFiles || isLoadingFile) return if (pendingMemoryPath) return @@ -169,40 +138,6 @@ export function MemorySettings() { void openFile(selectedProjectId, file.path) } - const togglePinnedProject = (projectId: string) => { - setProjectMenu(null) - setPinnedProjectIds((previous) => { - const next = new Set(previous) - if (next.has(projectId)) { - next.delete(projectId) - } else { - next.add(projectId) - } - writePinnedMemoryProjects(next) - return next - }) - } - - const openProjectInFinder = async (project: MemoryProject) => { - setProjectMenu(null) - try { - const store = useOpenTargetStore.getState() - await store.ensureTargets() - const latest = useOpenTargetStore.getState() - const target = latest.targets.find((item) => item.id === 'finder') - ?? latest.targets.find((item) => item.kind === 'file_manager') - if (!target) { - throw new Error(t('settings.memory.openInFinderUnavailable')) - } - await latest.openTarget(target.id, resolveMemoryProjectOpenPath(project)) - } catch (error) { - addToast({ - type: 'error', - message: error instanceof Error ? error.message : t('settings.memory.openInFinderFailed'), - }) - } - } - const toggleFolder = (path: string) => { setCollapsedFolders((previous) => { const next = new Set(previous) @@ -301,72 +236,32 @@ export function MemorySettings() {
{projects.length === 0 && !isLoadingProjects ? ( } text={t('settings.memory.emptyProjects')} /> - ) : orderedProjects.length === 0 ? ( + ) : filteredProjects.length === 0 ? ( } text={t('settings.memory.noProjectMatches')} /> ) : ( -
- {pinnedProjects.length > 0 ? ( - - {pinnedProjects.map((project) => { - const isExpanded = project.id === expandedProjectId - const isSelected = project.id === selectedProjectId - const visibleFileTree = isSelected ? fileTree : [] - return ( - handleProjectToggle(project.id)} - onOpenMenu={(event) => { - event.stopPropagation() - setProjectMenu(positionProjectMenu(project.id, event.clientX, event.clientY)) - }} - onToggleFolder={toggleFolder} - onFileSelect={handleFileOpen} - emptyText={t('settings.memory.emptyFiles')} - /> - ) - })} - - ) : null} - {regularProjects.length > 0 ? ( - 0 ? t('settings.memory.projects') : undefined}> - {regularProjects.map((project) => { - const isExpanded = project.id === expandedProjectId - const isSelected = project.id === selectedProjectId - const visibleFileTree = isSelected ? fileTree : [] - return ( - handleProjectToggle(project.id)} - onOpenMenu={(event) => { - event.stopPropagation() - setProjectMenu(positionProjectMenu(project.id, event.clientX, event.clientY)) - }} - onToggleFolder={toggleFolder} - onFileSelect={handleFileOpen} - emptyText={t('settings.memory.emptyFiles')} - /> - ) - })} - - ) : null} +
+ {filteredProjects.map((project) => { + const isExpanded = project.id === expandedProjectId + const isSelected = project.id === selectedProjectId + const visibleFileTree = isSelected ? fileTree : [] + return ( + handleProjectToggle(project.id)} + onToggleFolder={toggleFolder} + onFileSelect={handleFileOpen} + emptyText={t('settings.memory.emptyFiles')} + /> + ) + })}
)}
@@ -429,17 +324,6 @@ export function MemorySettings() { )}
- - {projectMenu ? ( - project.id === projectMenu.projectId) ?? null} - x={projectMenu.x} - y={projectMenu.y} - pinned={pinnedProjectIds.has(projectMenu.projectId)} - onTogglePin={() => togglePinnedProject(projectMenu.projectId)} - onOpenInFinder={(project) => void openProjectInFinder(project)} - /> - ) : null} ) } @@ -527,31 +411,16 @@ function PanelHeader({ icon, title, meta }: { icon?: ReactNode; title: string; m ) } -function MemoryProjectSection({ title, children }: { title?: string; children: ReactNode }) { - return ( -
- {title ? ( -
- {title} -
- ) : null} - {children} -
- ) -} - function ProjectTreeRow({ project, expanded, active, - pinned, loading, fileTree, activePath, collapsedFolders, forceExpanded, onToggle, - onOpenMenu, onToggleFolder, onFileSelect, emptyText, @@ -559,14 +428,12 @@ function ProjectTreeRow({ project: MemoryProject expanded: boolean active: boolean - pinned: boolean loading: boolean fileTree: MemoryTreeNode[] activePath: string | null collapsedFolders: Set forceExpanded: boolean onToggle: () => void - onOpenMenu: (event: MouseEvent) => void onToggleFolder: (path: string) => void onFileSelect: (file: MemoryFile) => void emptyText: string @@ -575,40 +442,25 @@ function ProjectTreeRow({ const display = projectDisplayName(project.label) return (
-
- - - - -
+