209 lines
8.9 KiB
JavaScript
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);
|
|
});
|
|
}
|
|
};
|
|
})();
|