Files
MyNote/apps/desktop/src/main/ipc/handlers.ts
T
hunter-dev dc8d844a1f feat(info): 信息面板 UI 重做 + 双向批量关联 + 代码块内全选复制
1. 信息面板 UI 重做:设计 token 统一、lucide 图标、分区小节、星标/标签/关联 chip 化
2. 关联文档升级:新增 doc:link-batch IPC,A↔B 双向写入 links;面板支持快速双向关联 + 批量勾选关联/解除
3. 代码块内全选复制:Ctrl+A 在代码块内仅全选该代码块(TyporaKeys selectInsideCodeBlock),Ctrl+C 复制纯代码;代码块内 Ctrl+V 按纯文本粘贴
4. 修复 React error #62:btn 改为函数后遗漏 style={btn} 裸用,导致 render 期间更新
2026-09-09 20:11:08 +08:00

96 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// IPC handlers — 主进程侧通道注册(类型链约束于 @hunter/shared)
import nodePath from 'node:path';
import { ipcMain, shell, dialog, clipboard } from 'electron';
import { IPC } from '@hunter/shared';
import type { VaultService } from '../services/vault-service.js';
import { buildGraphData } from '../services/graph-builder.js';
export function registerIpc(vault: VaultService) {
const fs2 = () => {
if (!vault.fileService) throw new Error('vault not initialized');
return vault.fileService;
};
ipcMain.handle(IPC.vaultList, async () => vault.getMeta());
ipcMain.handle(IPC.vaultSwitch, async (_e, p: { root: string }) => vault.switch(p.root));
ipcMain.handle(IPC.vaultSetDefault, async (_e, p: { root: string }) => { await vault.setDefaultRoot(p.root); });
ipcMain.handle(IPC.vaultPick, async () => vault.pickAndSwitch());
ipcMain.handle(IPC.vaultBrowse, async () => vault.pickDirectory());
ipcMain.handle(IPC.treeList, async () => fs2().listTree());
ipcMain.handle(IPC.treeRefresh, async () => fs2().listTree());
ipcMain.handle(IPC.fileCreate, async (_e, p: { basePath: string; name: string; isFolder: boolean }) =>
fs2().create(p.basePath, p.name, p.isFolder));
ipcMain.handle(IPC.fileRename, async (_e, p: { from: string; toName: string }) =>
fs2().rename(p.from, p.toName));
ipcMain.handle(IPC.fileMove, async (_e, p: { src: string; destDir: string }) =>
fs2().move(p.src, p.destDir));
// FR-F8:删除优先移入回收站(shell.trashItem),失败回退永久删除
ipcMain.handle(IPC.fileDelete, async (_e, p: { path: string; recursive: boolean }) => {
const abs = nodePath.join(fs2().getRoot(), p.path);
try {
await shell.trashItem(abs);
} catch (e) {
// 回退:回收站不可用时永久删除
console.error('trashItem failed, fallback to permanent delete:', e);
await fs2().delete(p.path, p.recursive);
}
});
// FR-F11:导入——sources 为空时弹系统对话框(支持文件夹导入)
ipcMain.handle(IPC.fileImport, async (_e, p: { sources: string[]; destDir: string }) => {
let sources = p.sources;
if (sources.length === 0) {
const r = await dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'], filters: [{ name: 'Markdown', extensions: ['md'] }] });
if (r.canceled) return { ok: [], failed: [] };
sources = r.filePaths;
}
return fs2().importFiles(sources, p.destDir);
});
// FR-F11:导入文件夹——选择外部文件夹递归复制进库
ipcMain.handle(IPC.fileImportFolder, async (_e, p: { destDir: string }) => {
const r = await dialog.showOpenDialog({ properties: ['openDirectory'] });
if (r.canceled || r.filePaths.length === 0) return { ok: [], failed: [] };
return fs2().importFiles(r.filePaths, p.destDir);
});
// FR-E6:图片入库
ipcMain.handle(IPC.assetPickImage, async () => vault.pickImage());
ipcMain.handle(IPC.assetWrite, async (_e, p: { name: string; dataBase64: string }) =>
fs2().writeAsset(p.name, p.dataBase64));
ipcMain.handle(IPC.docRead, async (_e, p: { path: string }) => fs2().readDoc(p.path));
ipcMain.handle(IPC.docWrite, async (_e, p: { path: string; meta: any; body: string }) =>
fs2().writeDoc({ path: p.path, meta: p.meta, body: p.body }));
// 双向批量关联:当前文档与多个目标文档互相写入 links
ipcMain.handle(IPC.docLinkBatch, async (_e, p: { path: string; targets: string[]; remove: boolean }) =>
fs2().linkBatch(p.path, p.targets, p.remove));
ipcMain.handle(IPC.search, async (_e, p: { query: string }) => vault.searchService?.search(p.query) ?? []);
ipcMain.handle(IPC.tagStats, async () => vault.searchService?.tagStats() ?? []);
ipcMain.handle(IPC.tagPaths, async () => vault.searchService?.tagPaths() ?? {});
ipcMain.handle(IPC.graph, async () => {
const fs2 = vault.fileService;
if (!fs2) return { nodes: [], edges: [] };
const tree = await fs2.listTree();
const gd = await buildGraphData(fs2.getRoot(), tree); return gd;
});
ipcMain.handle(IPC.appearanceLoad, async () => vault.getAppearance());
ipcMain.handle(IPC.appearanceSave, async (_e, p: { themeId: string; params: Record<string, unknown>; effect: string; bgImage: string | null }) => { await vault.saveAppearance(p); });
ipcMain.handle(IPC.keymapLoad, async () => vault.getKeymaps());
ipcMain.handle(IPC.keymapSave, async (_e, p: { id: string; combo: string }[]) => { await vault.saveKeymaps(p); });
ipcMain.handle(IPC.copyMd, async (_e, p: { path: string }) => {
const fs2 = vault.fileService; if (!fs2) return '';
const { body } = await fs2.readDoc(p.path);
clipboard.writeText(body);
return body;
});
ipcMain.handle(IPC.docReveal, async (_e, p: { path: string }) => {
const fs2 = vault.fileService;
if (!fs2) return;
shell.showItemInFolder(nodePath.join(fs2.getRoot(), p.path));
});
}