diff --git a/src/renderer/components/files/CurrentFilePanel.tsx b/src/renderer/components/files/CurrentFilePanel.tsx index c910c8f7..81d6a9fb 100644 --- a/src/renderer/components/files/CurrentFilePanel.tsx +++ b/src/renderer/components/files/CurrentFilePanel.tsx @@ -200,6 +200,13 @@ export function CurrentFilePanel({ rootPath, isActive = false }: CurrentFilePane [tabs, promptUnsaved, saveFile, closeFile, t] ); + const closeSavedTabs = useCallback(async () => { + const paths = tabs.filter((tab) => !tab.isDirty).map((tab) => tab.path); + for (const path of paths) { + await closeFile(path); + } + }, [tabs, closeFile]); + // Handle tab click const handleTabClick = useCallback( (path: string) => { @@ -266,6 +273,7 @@ export function CurrentFilePanel({ rootPath, isActive = false }: CurrentFilePane const paths = tabs.map((t) => t.path); await requestCloseTabs(paths); }} + onCloseSaved={closeSavedTabs} onCloseLeft={async (path) => { const index = tabs.findIndex((t) => t.path === path); if (index <= 0) return; diff --git a/src/renderer/components/files/EditorArea.tsx b/src/renderer/components/files/EditorArea.tsx index 68b4e27d..65920ff2 100644 --- a/src/renderer/components/files/EditorArea.tsx +++ b/src/renderer/components/files/EditorArea.tsx @@ -1,5 +1,14 @@ import Editor, { type OnMount } from '@monaco-editor/react'; -import { ChevronRight, Eye, EyeOff, FileCode, FileX, Maximize2, MessageSquare } from 'lucide-react'; +import { + ChevronRight, + Eye, + EyeOff, + FileCode, + FileX, + Maximize2, + MessageSquare, + MoreVertical, +} from 'lucide-react'; import type * as monaco from 'monaco-editor'; import { forwardRef, @@ -18,6 +27,7 @@ import { BreadcrumbList, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; +import { Button } from '@/components/ui/button'; import { Empty, EmptyDescription, @@ -25,6 +35,7 @@ import { EmptyMedia, EmptyTitle, } from '@/components/ui/empty'; +import { Menu, MenuItem, MenuPopup, MenuTrigger } from '@/components/ui/menu'; import { addToast } from '@/components/ui/toast'; import { useDebouncedSave } from '@/hooks/useDebouncedSave'; import { useI18n } from '@/i18n'; @@ -139,6 +150,7 @@ interface EditorAreaProps { onTabClose: (path: string) => void | Promise; onCloseOthers?: (keepPath: string) => void | Promise; onCloseAll?: () => void | Promise; + onCloseSaved?: () => void | Promise; onCloseLeft?: (path: string) => void | Promise; onCloseRight?: (path: string) => void | Promise; onTabReorder: (fromIndex: number, toIndex: number) => void; @@ -163,6 +175,7 @@ export const EditorArea = forwardRef(function Ed onTabClose, onCloseOthers, onCloseAll, + onCloseSaved, onCloseLeft, onCloseRight, onTabReorder, @@ -1287,7 +1300,7 @@ export const EditorArea = forwardRef(function Ed return (
{/* Tabs */} -
+
{isFileTreeCollapsed && onToggleFileTree && (
+ {tabs.length > 0 && (onCloseSaved || onCloseAll) && ( + + + + + } + /> + + !tab.isDirty)} + onClick={async () => { + if (onCloseSaved) await onCloseSaved(); + }} + > + {t('Close Saved Tabs')} + + { + if (!onCloseAll) return; + await onCloseAll(); + }} + > + {t('Close All Tabs')} + + + + )} {/* Markdown Preview Toggle */} {isMarkdown && (