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 =====
|
// ===== Tab Operations =====
|
||||||
function getActiveTab() {
|
function getActiveTab() {
|
||||||
return tabs.find(t => t.id === activeTabId) || null;
|
return tabs.find(t => t.id === activeTabId) || null;
|
||||||
@@ -589,6 +619,15 @@
|
|||||||
// Update modified state
|
// Update modified state
|
||||||
setModified(tab.isModified);
|
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
|
// Notify main process about active file
|
||||||
if (typeof window.electronAPI !== 'undefined') {
|
if (typeof window.electronAPI !== 'undefined') {
|
||||||
window.electronAPI.tabSwitched(tab.filePath);
|
window.electronAPI.tabSwitched(tab.filePath);
|
||||||
@@ -638,6 +677,7 @@
|
|||||||
loadTabState(tab);
|
loadTabState(tab);
|
||||||
renderTabBar();
|
renderTabBar();
|
||||||
updateTitle();
|
updateTitle();
|
||||||
|
updateSidebarHighlight();
|
||||||
welcomeScreen.classList.add('hidden');
|
welcomeScreen.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,6 +724,7 @@
|
|||||||
|
|
||||||
renderTabBar();
|
renderTabBar();
|
||||||
updateTitle();
|
updateTitle();
|
||||||
|
updateSidebarHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== File Size Limit =====
|
// ===== File Size Limit =====
|
||||||
@@ -1379,6 +1420,7 @@
|
|||||||
if (tab && tab.filePath === node.path) {
|
if (tab && tab.filePath === node.path) {
|
||||||
item.classList.add('active');
|
item.classList.add('active');
|
||||||
}
|
}
|
||||||
|
item.dataset.path = node.path;
|
||||||
|
|
||||||
parent.appendChild(item);
|
parent.appendChild(item);
|
||||||
|
|
||||||
@@ -1407,11 +1449,78 @@
|
|||||||
return lastTreeData || [];
|
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;
|
const _originalRenderTree = renderTree;
|
||||||
renderTree = function(tree, rootPath) {
|
renderTree = function(tree, rootPath) {
|
||||||
lastTreeData = tree;
|
lastTreeData = tree;
|
||||||
_originalRenderTree(tree, rootPath);
|
_originalRenderTree(tree, rootPath);
|
||||||
|
// Prepend independent files section
|
||||||
|
const indepSection = renderIndependentFiles();
|
||||||
|
if (indepSection) {
|
||||||
|
sidebarTree.insertBefore(indepSection, sidebarTree.firstChild);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== Search Bar Init =====
|
// ===== Search Bar Init =====
|
||||||
|
|||||||
@@ -860,6 +860,28 @@ html, body {
|
|||||||
text-overflow: ellipsis;
|
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 & Replace Bar ===== */
|
||||||
#search-bar {
|
#search-bar {
|
||||||
background: var(--search-bg);
|
background: var(--search-bg);
|
||||||
|
|||||||
Reference in New Issue
Block a user