Skip to content

Commit 858d020

Browse files
committed
feat: file rank sorting, tab close confirmation, middle-click close
- FileRankDialog: sort by size/name/type with toggle buttons - closeTab checks if file is being edited, shows confirmation dialog - TabBar: middle-click (button 1) to close tabs - Proper cleanup of editingPath on tab close
1 parent bf22628 commit 858d020

3 files changed

Lines changed: 54 additions & 3 deletions

File tree

structure-insight/components/FileRankDialog.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ interface FileRankDialogProps {
1414
}
1515

1616
const FileRankDialog: React.FC<FileRankDialogProps> = ({ isOpen, onClose, files, onSelectFile, onCopyPath, onDeleteFile, onToggleExclude }) => {
17+
const [sortBy, setSortBy] = React.useState<'size' | 'name' | 'type'>('size');
18+
1719
// Prevent interaction with background and handle Esc
1820
React.useEffect(() => {
1921
const handleKeyDown = (e: KeyboardEvent) => {
@@ -26,8 +28,17 @@ const FileRankDialog: React.FC<FileRankDialogProps> = ({ isOpen, onClose, files,
2628
}, [isOpen, onClose]);
2729

2830
const sortedFiles = React.useMemo(() => {
29-
return [...files].sort((a, b) => b.stats.chars - a.stats.chars);
30-
}, [files]);
31+
const sorted = [...files];
32+
if (sortBy === 'size') {
33+
sorted.sort((a, b) => b.stats.chars - a.stats.chars);
34+
} else if (sortBy === 'name') {
35+
sorted.sort((a, b) => a.path.localeCompare(b.path));
36+
} else {
37+
const getExt = (path: string) => { const dot = path.lastIndexOf('.'); return dot >= 0 ? path.slice(dot) : ''; };
38+
sorted.sort((a, b) => getExt(a.path).localeCompare(getExt(b.path)) || a.path.localeCompare(b.path));
39+
}
40+
return sorted;
41+
}, [files, sortBy]);
3142

3243
const maxChars = sortedFiles.length > 0 ? sortedFiles[0].stats.chars : 0;
3344

@@ -72,6 +83,24 @@ const FileRankDialog: React.FC<FileRankDialogProps> = ({ isOpen, onClose, files,
7283
</button>
7384
</div>
7485

86+
{/* Sort Options */}
87+
<div className="flex items-center gap-1.5 px-6 py-2 border-b border-light-border dark:border-dark-border bg-light-bg/30 dark:bg-dark-bg/30 shrink-0">
88+
<span className="text-xs text-light-subtle-text dark:text-dark-subtle-text mr-1">排序:</span>
89+
{([['size', '大小'], ['name', '名称'], ['type', '类型']] as const).map(([key, label]) => (
90+
<button
91+
key={key}
92+
onClick={() => setSortBy(key)}
93+
className={`px-2 py-0.5 rounded text-xs font-medium transition-colors ${
94+
sortBy === key
95+
? 'bg-primary text-white'
96+
: 'bg-light-bg dark:bg-dark-bg text-light-subtle-text dark:text-dark-subtle-text hover:bg-light-border dark:hover:bg-dark-border'
97+
}`}
98+
>
99+
{label}
100+
</button>
101+
))}
102+
</div>
103+
75104
{/* List */}
76105
<div className="flex-1 overflow-y-auto p-4 space-y-2">
77106
{sortedFiles.length === 0 ? (

structure-insight/components/TabBar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const TabBar: React.FC<TabBarProps> = ({ openFiles, selectedFilePath, onTabSelec
2323
<div
2424
key={path}
2525
onClick={() => onTabSelect(path)}
26+
onAuxClick={(e) => { if (e.button === 1) { e.preventDefault(); onCloseTab(path); } }}
2627
className={`group flex items-center gap-1.5 px-3 py-1.5 cursor-pointer border-b-2 transition-colors shrink-0 text-xs ${
2728
isActive
2829
? 'border-primary bg-primary/10 text-light-text dark:text-dark-text'

structure-insight/hooks/useAppLogic.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,27 @@ export const useAppLogic = (
8282
}, [handleFileTreeSelect]);
8383

8484
const closeTab = React.useCallback((path: string) => {
85+
if (editingPath === path) {
86+
setConfirmation({
87+
isOpen: true,
88+
title: '文件正在编辑中',
89+
message: '关闭标签页将丢失未保存的更改。是否继续?',
90+
onConfirm: () => {
91+
setEditingPath(null);
92+
setOpenFiles(prev => {
93+
const next = prev.filter(p => p !== path);
94+
if (path === selectedFilePath) {
95+
const closedIdx = prev.indexOf(path);
96+
const newSelected = next[Math.min(closedIdx, next.length - 1)] ?? null;
97+
setSelectedFilePath(newSelected);
98+
if (!newSelected) setActiveView('structure');
99+
}
100+
return next;
101+
});
102+
}
103+
});
104+
return;
105+
}
85106
setOpenFiles(prev => {
86107
const next = prev.filter(p => p !== path);
87108
if (path === selectedFilePath) {
@@ -92,7 +113,7 @@ export const useAppLogic = (
92113
}
93114
return next;
94115
});
95-
}, [selectedFilePath, setSelectedFilePath, setActiveView]);
116+
}, [editingPath, selectedFilePath, setSelectedFilePath, setActiveView, setConfirmation, setEditingPath]);
96117

97118
// --- Search Hook ---
98119
const {

0 commit comments

Comments
 (0)