fix: 侧边栏高亮跟随标签切换同步更新,支持显示独立打开的文件

- 添加 updateSidebarHighlight() 函数,标签切换时只更新高亮状态而非重建整棵树
- 给树节点添加 data-path 属性用于精确定位高亮
- 新增「已打开的文件」区域,显示不在当前文件夹内的独立文件
- 独立文件支持点击切换、修改状态标识、活跃高亮
- 无文件夹打开时自动显示独立文件列表,全部关闭后隐藏侧边栏
This commit is contained in:
thzxx
2026-05-21 12:56:50 +08:00
parent 65383defd1
commit eff04606bd
2 changed files with 132 additions and 1 deletions
+110 -1
View File
@@ -539,6 +539,36 @@
}
}
// ===== Sidebar Highlight =====
// Update sidebar active highlight without re-rendering the entire tree
function updateSidebarHighlight() {
const tab = getActiveTab();
const activePath = tab ? tab.filePath : null;
// If no folder is open, just re-render independent files
if (!currentRootPath) {
sidebarTree.innerHTML = '';
const indepSection = renderIndependentFiles();
if (indepSection) sidebarTree.appendChild(indepSection);
return;
}
// Re-render independent files section (files may have been added/removed)
const oldSection = sidebarTree.querySelector('.independent-files-section');
const newSection = renderIndependentFiles();
if (oldSection) oldSection.remove();
if (newSection) sidebarTree.insertBefore(newSection, sidebarTree.firstChild);
// Update folder tree items
const treeItems = sidebarTree.querySelectorAll('.tree-item:not(.independent-file-item)');
treeItems.forEach(item => {
const itemPath = item.dataset.path;
if (itemPath) {
item.classList.toggle('active', itemPath === activePath);
}
});
}
// ===== Tab Operations =====
function getActiveTab() {
return tabs.find(t => t.id === activeTabId) || null;
@@ -589,6 +619,15 @@
// Update modified state
setModified(tab.isModified);
// Update sidebar highlight and independent files
updateSidebarHighlight();
// If no folder is open but we have open files, ensure sidebar is visible
if (!currentRootPath && tabs.length > 0) {
sidebar.classList.remove('collapsed');
} else if (!currentRootPath && tabs.length === 0) {
sidebar.classList.add('collapsed');
}
// Notify main process about active file
if (typeof window.electronAPI !== 'undefined') {
window.electronAPI.tabSwitched(tab.filePath);
@@ -638,6 +677,7 @@
loadTabState(tab);
renderTabBar();
updateTitle();
updateSidebarHighlight();
welcomeScreen.classList.add('hidden');
}
@@ -684,6 +724,7 @@
renderTabBar();
updateTitle();
updateSidebarHighlight();
}
// ===== File Size Limit =====
@@ -1379,6 +1420,7 @@
if (tab && tab.filePath === node.path) {
item.classList.add('active');
}
item.dataset.path = node.path;
parent.appendChild(item);
@@ -1407,11 +1449,78 @@
return lastTreeData || [];
}
// Override renderTree to cache data
// Build list of open files that are NOT inside currentRootPath
function getIndependentFiles() {
if (!tabs.length) return [];
return tabs.filter(t => {
if (!t.filePath) return false; // skip unnamed tabs
if (!currentRootPath) return true; // no folder open → all files are "independent"
// Check if file is inside the current root path
return !t.filePath.startsWith(currentRootPath);
});
}
function renderIndependentFiles() {
const files = getIndependentFiles();
if (files.length === 0) return null;
const section = document.createElement('div');
section.className = 'independent-files-section';
const header = document.createElement('div');
header.className = 'independent-files-header';
header.textContent = '已打开的文件';
section.appendChild(header);
for (const tab of files) {
const item = document.createElement('div');
item.className = 'tree-item independent-file-item';
item.style.paddingLeft = '8px';
item.dataset.path = tab.filePath;
const icon = document.createElement('span');
icon.className = 'tree-icon';
icon.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>';
item.appendChild(icon);
const name = document.createElement('span');
name.className = 'tree-name';
name.textContent = getFileName(tab.filePath);
item.appendChild(name);
// Mark as modified
if (tab.isModified) {
const dot = document.createElement('span');
dot.className = 'independent-modified-dot';
dot.textContent = ' •';
item.appendChild(dot);
}
// Highlight if active
if (tab.id === activeTabId) {
item.classList.add('active');
}
item.addEventListener('click', (e) => {
e.stopPropagation();
switchToTab(tab.id);
});
section.appendChild(item);
}
return section;
}
// Override renderTree to cache data and prepend independent files
const _originalRenderTree = renderTree;
renderTree = function(tree, rootPath) {
lastTreeData = tree;
_originalRenderTree(tree, rootPath);
// Prepend independent files section
const indepSection = renderIndependentFiles();
if (indepSection) {
sidebarTree.insertBefore(indepSection, sidebarTree.firstChild);
}
};
// ===== Search Bar Init =====
+22
View File
@@ -860,6 +860,28 @@ html, body {
text-overflow: ellipsis;
}
/* ===== Independent Files Section ===== */
.independent-files-section {
border-bottom: 1px solid var(--border);
margin-bottom: 4px;
padding-bottom: 4px;
}
.independent-files-header {
font-size: 11px;
font-weight: 600;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 8px 12px 4px 12px;
}
.independent-file-item .independent-modified-dot {
color: var(--primary);
font-size: 14px;
margin-left: 4px;
}
/* ===== Search & Replace Bar ===== */
#search-bar {
background: var(--search-bg);