add show in finder

This commit is contained in:
Arjun 2026-05-15 12:11:50 +05:30
parent 41f783d504
commit f9ddc6549a
5 changed files with 46 additions and 1 deletions

View file

@ -679,6 +679,11 @@ export function setupIpcHandlers() {
const error = await shell.openPath(filePath); const error = await shell.openPath(filePath);
return { error: error || undefined }; return { error: error || undefined };
}, },
'shell:showItemInFolder': async (_event, args) => {
const filePath = resolveShellPath(args.path);
shell.showItemInFolder(filePath);
return { success: true };
},
'shell:readFileBase64': async (_event, args) => { 'shell:readFileBase64': async (_event, args) => {
const filePath = resolveShellPath(args.path); const filePath = resolveShellPath(args.path);
const stat = await fs.stat(filePath); const stat = await fs.stat(filePath);

View file

@ -3966,6 +3966,12 @@ function App() {
document.body.removeChild(textarea) document.body.removeChild(textarea)
}) })
}, },
revealInFileManager: (path: string, isDir: boolean) => {
const channel = isDir ? 'shell:openPath' : 'shell:showItemInFolder'
void window.ipc.invoke(channel, { path }).catch((err) => {
console.error('Failed to open in file manager:', err)
})
},
onOpenInNewTab: (path: string) => { onOpenInNewTab: (path: string) => {
openFileInNewTab(path) openFileInNewTab(path)
}, },
@ -4769,6 +4775,7 @@ function App() {
rename: knowledgeActions.rename, rename: knowledgeActions.rename,
remove: knowledgeActions.remove, remove: knowledgeActions.remove,
copyPath: knowledgeActions.copyPath, copyPath: knowledgeActions.copyPath,
revealInFileManager: knowledgeActions.revealInFileManager,
}} }}
/> />
</div> </div>

View file

@ -1,6 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { useEffect, useState, useMemo, useCallback, useRef } from 'react' import { useEffect, useState, useMemo, useCallback, useRef } from 'react'
import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, X, Check, ListFilter, Filter, Search, Save, Copy, Pencil, Trash2 } from 'lucide-react' import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, X, Check, ListFilter, Filter, Search, Save, Copy, FolderOpen, Pencil, Trash2 } from 'lucide-react'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover' import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'
import { Command, CommandInput, CommandList, CommandItem, CommandEmpty, CommandGroup } from '@/components/ui/command' import { Command, CommandInput, CommandList, CommandItem, CommandEmpty, CommandGroup } from '@/components/ui/command'
@ -103,9 +103,18 @@ type BasesViewProps = {
rename: (oldPath: string, newName: string, isDir: boolean) => Promise<void> rename: (oldPath: string, newName: string, isDir: boolean) => Promise<void>
remove: (path: string) => Promise<void> remove: (path: string) => Promise<void>
copyPath: (path: string) => void copyPath: (path: string) => void
revealInFileManager: (path: string, isDir: boolean) => void
} }
} }
function getFileManagerName(): string {
if (typeof navigator === 'undefined') return 'File Manager'
const platform = navigator.platform.toLowerCase()
if (platform.includes('mac')) return 'Finder'
if (platform.includes('win')) return 'Explorer'
return 'File Manager'
}
function collectFiles(nodes: TreeNode[]): { path: string; name: string; mtimeMs: number }[] { function collectFiles(nodes: TreeNode[]): { path: string; name: string; mtimeMs: number }[] {
return nodes.flatMap((n) => return nodes.flatMap((n) =>
n.kind === 'file' && n.name.endsWith('.md') n.kind === 'file' && n.name.endsWith('.md')
@ -919,6 +928,10 @@ function NoteRow({
<Copy className="mr-2 size-4" /> <Copy className="mr-2 size-4" />
Copy Path Copy Path
</ContextMenuItem> </ContextMenuItem>
<ContextMenuItem onClick={() => actions?.revealInFileManager(note.path, false)}>
<FolderOpen className="mr-2 size-4" />
Open in {getFileManagerName()}
</ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ContextMenuItem onClick={() => { setNewName(baseName); isSubmittingRef.current = false; setIsRenaming(true) }}> <ContextMenuItem onClick={() => { setNewName(baseName); isSubmittingRef.current = false; setIsRenaming(true) }}>
<Pencil className="mr-2 size-4" /> <Pencil className="mr-2 size-4" />

View file

@ -11,6 +11,7 @@ import {
ExternalLink, ExternalLink,
FilePlus, FilePlus,
Folder, Folder,
FolderOpen,
FolderPlus, FolderPlus,
Globe, Globe,
AlertTriangle, AlertTriangle,
@ -119,9 +120,18 @@ type KnowledgeActions = {
rename: (path: string, newName: string, isDir: boolean) => Promise<void> rename: (path: string, newName: string, isDir: boolean) => Promise<void>
remove: (path: string) => Promise<void> remove: (path: string) => Promise<void>
copyPath: (path: string) => void copyPath: (path: string) => void
revealInFileManager: (path: string, isDir: boolean) => void
onOpenInNewTab?: (path: string) => void onOpenInNewTab?: (path: string) => void
} }
function getFileManagerName(): string {
if (typeof navigator === 'undefined') return 'File Manager'
const platform = navigator.platform.toLowerCase()
if (platform.includes('mac')) return 'Finder'
if (platform.includes('win')) return 'Explorer'
return 'File Manager'
}
type RunListItem = { type RunListItem = {
id: string id: string
title?: string title?: string
@ -1177,11 +1187,13 @@ function KnowledgeSection({
}, },
}), [actions, deriveParent]) }), [actions, deriveParent])
const fileManagerName = getFileManagerName()
const quickActions = [ const quickActions = [
{ icon: FilePlus, label: "New Note", action: () => wrappedActions.createNote() }, { icon: FilePlus, label: "New Note", action: () => wrappedActions.createNote() },
{ icon: FolderPlus, label: "New Folder", action: () => void wrappedActions.createFolder() }, { icon: FolderPlus, label: "New Folder", action: () => void wrappedActions.createFolder() },
{ icon: Network, label: "Graph View", action: () => actions.openGraph() }, { icon: Network, label: "Graph View", action: () => actions.openGraph() },
{ icon: Table2, label: "Bases", action: () => actions.openBases() }, { icon: Table2, label: "Bases", action: () => actions.openBases() },
{ icon: FolderOpen, label: `Open in ${fileManagerName}`, action: () => actions.revealInFileManager('knowledge', true) },
] ]
return ( return (
@ -1389,6 +1401,10 @@ function Tree({
<Copy className="mr-2 size-4" /> <Copy className="mr-2 size-4" />
Copy Path Copy Path
</ContextMenuItem> </ContextMenuItem>
<ContextMenuItem onClick={() => actions.revealInFileManager(item.path, isDir)}>
<FolderOpen className="mr-2 size-4" />
Open in {getFileManagerName()}
</ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ContextMenuItem onClick={() => { setNewName(baseName); isSubmittingRef.current = false; setIsRenaming(true) }}> <ContextMenuItem onClick={() => { setNewName(baseName); isSubmittingRef.current = false; setIsRenaming(true) }}>
<Pencil className="mr-2 size-4" /> <Pencil className="mr-2 size-4" />

View file

@ -489,6 +489,10 @@ const ipcSchemas = {
req: z.object({ path: z.string() }), req: z.object({ path: z.string() }),
res: z.object({ error: z.string().optional() }), res: z.object({ error: z.string().optional() }),
}, },
'shell:showItemInFolder': {
req: z.object({ path: z.string() }),
res: z.object({ success: z.literal(true) }),
},
'shell:readFileBase64': { 'shell:readFileBase64': {
req: z.object({ path: z.string() }), req: z.object({ path: z.string() }),
res: z.object({ data: z.string(), mimeType: z.string(), size: z.number() }), res: z.object({ data: z.string(), mimeType: z.string(), size: z.number() }),