fix: 侧边栏高亮跟随标签切换同步更新,支持显示独立打开的文件
- 添加 updateSidebarHighlight() 函数,标签切换时只更新高亮状态而非重建整棵树 - 给树节点添加 data-path 属性用于精确定位高亮 - 新增「已打开的文件」区域,显示不在当前文件夹内的独立文件 - 独立文件支持点击切换、修改状态标识、活跃高亮 - 无文件夹打开时自动显示独立文件列表,全部关闭后隐藏侧边栏
This commit is contained in:
+110
-1
@@ -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 =====
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user