feat: v1.2.0 搜索替换 + 文件树侧边栏
This commit is contained in:
@@ -305,6 +305,88 @@ ipcMain.handle('file:reload', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// ===== File Tree (Sidebar) =====
|
||||
const SKIP_DIRS = new Set(['node_modules', '.git', '.svn', '.hg', 'dist', 'out', '.next', '.nuxt', '__pycache__', '.DS_Store']);
|
||||
const MARKDOWN_EXTS = new Set(['.md', '.markdown', '.txt']);
|
||||
|
||||
async function buildDirTree(dirPath) {
|
||||
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||
// Sort: directories first, then files, alphabetically
|
||||
entries.sort((a, b) => {
|
||||
if (a.isDirectory() && !b.isDirectory()) return -1;
|
||||
if (!a.isDirectory() && b.isDirectory()) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
const children = [];
|
||||
for (const entry of entries) {
|
||||
if (SKIP_DIRS.has(entry.name)) continue;
|
||||
if (entry.name.startsWith('.')) continue;
|
||||
|
||||
const childPath = path.join(dirPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
const subChildren = await buildDirTree(childPath);
|
||||
if (subChildren.length > 0) {
|
||||
children.push({ name: entry.name, path: childPath, type: 'dir', children: subChildren });
|
||||
}
|
||||
} else {
|
||||
const ext = path.extname(entry.name).toLowerCase();
|
||||
if (MARKDOWN_EXTS.has(ext)) {
|
||||
children.push({ name: entry.name, path: childPath, type: 'file' });
|
||||
}
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
ipcMain.handle('dir:readTree', async (event, dirPath) => {
|
||||
try {
|
||||
const tree = await buildDirTree(dirPath);
|
||||
return { success: true, tree, rootPath: dirPath };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
});
|
||||
|
||||
let sidebarWatcher = null;
|
||||
let sidebarWatchPath = null;
|
||||
|
||||
function startSidebarWatching(dirPath) {
|
||||
stopSidebarWatching();
|
||||
if (!dirPath) return;
|
||||
try {
|
||||
sidebarWatchPath = dirPath;
|
||||
sidebarWatcher = fs.watch(dirPath, { recursive: true }, (eventType, filename) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('sidebar:dirChanged');
|
||||
}
|
||||
});
|
||||
} catch (err) { /* ignore */ }
|
||||
}
|
||||
|
||||
function stopSidebarWatching() {
|
||||
if (sidebarWatcher) { sidebarWatcher.close(); sidebarWatcher = null; }
|
||||
sidebarWatchPath = null;
|
||||
}
|
||||
|
||||
ipcMain.handle('dir:watch', (event, dirPath) => {
|
||||
startSidebarWatching(dirPath);
|
||||
});
|
||||
|
||||
ipcMain.handle('dir:unwatch', () => {
|
||||
stopSidebarWatching();
|
||||
});
|
||||
|
||||
ipcMain.handle('dir:openDialog', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// Tab switched - update active file tracking
|
||||
ipcMain.handle('tab:switched', (event, filePath) => {
|
||||
switchActiveFile(filePath);
|
||||
|
||||
Reference in New Issue
Block a user