Files
MarkLite/renderer/sidebar.js
T

209 lines
8.9 KiB
JavaScript

// MarkLite — Sidebar Module: file tree, independent files, resize handle
(function () {
'use strict';
var ML = window.MarkLite;
var expandedDirs = new Set();
var lastTreeData = null;
// ===== Sidebar Highlight =====
ML.updateSidebarHighlight = function () {
var tab = ML.getActiveTab();
var activePath = tab ? tab.filePath : null;
if (!ML.currentRootPath) {
ML.sidebarTree.innerHTML = '';
var indep = renderIndependentFiles();
if (indep) ML.sidebarTree.appendChild(indep);
return;
}
var oldSection = ML.sidebarTree.querySelector('.independent-files-section');
var newSection = renderIndependentFiles();
if (oldSection) oldSection.remove();
if (newSection) ML.sidebarTree.insertBefore(newSection, ML.sidebarTree.firstChild);
var treeItems = ML.sidebarTree.querySelectorAll('.tree-item:not(.independent-file-item)');
treeItems.forEach(function (item) {
var itemPath = item.dataset.path;
if (itemPath) item.classList.toggle('active', itemPath === activePath);
});
};
// ===== Independent Files =====
function getIndependentFiles() {
if (!ML.tabs.length) return [];
return ML.tabs.filter(function (t) {
if (!t.filePath) return false;
if (!ML.currentRootPath) return true;
return !t.filePath.startsWith(ML.currentRootPath);
});
}
function renderIndependentFiles() {
var files = getIndependentFiles();
if (files.length === 0) return null;
var section = document.createElement('div');
section.className = 'independent-files-section';
var header = document.createElement('div');
header.className = 'independent-files-header';
header.textContent = '已打开的文件';
section.appendChild(header);
files.forEach(function (tab) {
var item = document.createElement('div');
item.className = 'tree-item independent-file-item';
item.style.paddingLeft = '8px';
item.dataset.path = tab.filePath;
var 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);
var name = document.createElement('span');
name.className = 'tree-name';
name.textContent = ML.getFileName(tab.filePath);
item.appendChild(name);
if (tab.isModified) {
var dot = document.createElement('span');
dot.className = 'independent-modified-dot';
dot.textContent = ' •';
item.appendChild(dot);
}
if (tab.id === ML.activeTabId) item.classList.add('active');
item.addEventListener('click', function (e) { e.stopPropagation(); ML.switchToTab(tab.id); });
section.appendChild(item);
});
return section;
}
// ===== Folder Operations =====
async function openFolderDialog() {
if (typeof window.electronAPI !== 'undefined' && window.electronAPI.openFolderDialog) {
var result = await window.electronAPI.openFolderDialog();
if (result) loadFolder(result);
}
}
async function loadFolder(dirPath) {
if (typeof window.electronAPI === 'undefined') return;
ML.currentRootPath = dirPath;
var result = await window.electronAPI.readDirTree(dirPath);
if (result.success) {
renderTree(result.tree, dirPath);
ML.sidebar.classList.remove('collapsed');
window.electronAPI.watchDir(dirPath);
}
}
async function refreshTree() {
if (!ML.currentRootPath) return;
var result = await window.electronAPI.readDirTree(ML.currentRootPath);
if (result.success) renderTree(result.tree, ML.currentRootPath);
}
// ===== Tree Rendering =====
function renderTreeOriginal(tree, rootPath) {
ML.sidebarTree.innerHTML = '';
var rootNode = { name: ML.getFileName(rootPath) || rootPath, path: rootPath, type: 'dir', children: tree };
expandedDirs.add(rootPath);
var fragment = document.createDocumentFragment();
buildTreeDOM(fragment, rootNode, 0);
ML.sidebarTree.appendChild(fragment);
}
function renderTree(tree, rootPath) {
lastTreeData = tree;
renderTreeOriginal(tree, rootPath);
var folderHeader = document.createElement('div');
folderHeader.className = 'sidebar-section-header';
folderHeader.textContent = '文件夹目录树';
ML.sidebarTree.insertBefore(folderHeader, ML.sidebarTree.firstChild);
var indepSection = renderIndependentFiles();
if (indepSection) ML.sidebarTree.insertBefore(indepSection, ML.sidebarTree.firstChild);
}
function buildTreeDOM(parent, node, depth) {
var item = document.createElement('div');
item.className = 'tree-item';
item.style.paddingLeft = (8 + depth * 16) + 'px';
if (node.type === 'dir') {
var isExpanded = expandedDirs.has(node.path);
var arrow = document.createElement('span');
arrow.className = 'tree-arrow' + (isExpanded ? ' expanded' : '');
arrow.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"></polyline></svg>';
item.appendChild(arrow);
var 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="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>';
item.appendChild(icon);
var name = document.createElement('span');
name.className = 'tree-name';
name.textContent = node.name;
item.appendChild(name);
parent.appendChild(item);
if (isExpanded && node.children) {
node.children.forEach(function (child) { buildTreeDOM(parent, child, depth + 1); });
}
item.addEventListener('click', function (e) {
e.stopPropagation();
if (expandedDirs.has(node.path)) expandedDirs.delete(node.path);
else expandedDirs.add(node.path);
renderTree(lastTreeData || [], ML.currentRootPath);
});
} else {
var spacer = document.createElement('span');
spacer.style.width = '16px';
spacer.style.flexShrink = '0';
item.appendChild(spacer);
var fileIcon = document.createElement('span');
fileIcon.className = 'tree-icon';
fileIcon.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(fileIcon);
var fileName = document.createElement('span');
fileName.className = 'tree-name';
fileName.textContent = node.name;
item.appendChild(fileName);
var tab = ML.getActiveTab();
if (tab && tab.filePath === node.path) item.classList.add('active');
item.dataset.path = node.path;
parent.appendChild(item);
item.addEventListener('click', async function (e) {
e.stopPropagation();
var existing = ML.tabs.find(function (t) { return t.filePath === node.path; });
if (existing) { ML.switchToTab(existing.id); }
else if (typeof window.electronAPI !== 'undefined') {
var result = await window.electronAPI.readFile(node.path);
if (result.success) ML.createNewTab(node.path, result.content);
}
});
}
}
// ===== Init =====
ML.initSidebar = function () {
var settings = ML.loadSettings();
if (settings.sidebarCollapsed) ML.sidebar.classList.add('collapsed');
if (settings.sidebarWidth) ML.sidebar.style.width = settings.sidebarWidth + 'px';
// Resize handle
var handle = document.createElement('div');
handle.className = 'sidebar-resize-handle';
ML.sidebar.appendChild(handle);
var isResizing = false;
handle.addEventListener('mousedown', function (e) { isResizing = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; e.preventDefault(); });
document.addEventListener('mousemove', function (e) {
if (!isResizing) return;
var newWidth = Math.max(180, Math.min(500, e.clientX));
ML.sidebar.style.width = newWidth + 'px';
ML.saveSetting('sidebarWidth', newWidth);
});
document.addEventListener('mouseup', function () { if (isResizing) { isResizing = false; document.body.style.cursor = ''; document.body.style.userSelect = ''; } });
ML.btnOpenFolder.addEventListener('click', function () { if (typeof window.electronAPI !== 'undefined') openFolderDialog(); });
if (typeof window.electronAPI !== 'undefined') {
var dirRefreshTimer = null;
window.electronAPI.onDirChanged(function () {
if (!ML.currentRootPath) return;
if (dirRefreshTimer) clearTimeout(dirRefreshTimer);
dirRefreshTimer = setTimeout(function () { refreshTree(); }, 300);
});
}
};
})();