feat(M2): FR-E6 链接气泡+图片入库——LinkBubbleMenu(编辑/移除href);ImageView 节点视图(相对路径转 vaultasset:// 协议展示);assetPickImage/assetWrite IPC + .attachments 入库;剪贴板粘贴图片入库;工具栏 链接/图片/网图 按钮;CSP 放行 vaultasset:;editor-link-image E2E 3 项

This commit is contained in:
hunter-dev
2026-08-26 13:32:13 +08:00
parent 4beebbc8d0
commit 2f4bcfd1e4
11 changed files with 295 additions and 5 deletions
+22 -2
View File
@@ -1,13 +1,19 @@
// main 进程入口:创建窗口 + 初始化库 + 注册 IPC + 迁移检测
import { app, BrowserWindow, ipcMain } from 'electron';
import { app, BrowserWindow, ipcMain, protocol, net } from 'electron';
import nodePath from 'node:path';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { VaultService } from './services/vault-service.js';
import { registerIpc } from './ipc/handlers.js';
import { MigrationService } from './services/migration-service.js';
import { IPC } from '@hunter/shared';
const __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
// FR-E6:注册 vaultasset:// 协议——渲染层用相对路径展示库内本地图片(须在 app ready 前注册)
protocol.registerSchemesAsPrivileged([
{ scheme: 'vaultasset', privileges: { standard: true, secure: true, supportFetchAPI: true, stream: true } },
]);
let win: BrowserWindow | null = null;
function createWindow() {
@@ -34,6 +40,20 @@ function createWindow() {
app.whenReady().then(async () => {
const vault = new VaultService();
await vault.init();
// FR-E6:vaultasset:// 协议处理器——安全提供库内文件给渲染层(限定在库根内)
protocol.handle('vaultasset', async (req) => {
const root = vault.fileService?.getRoot();
if (!root) return new Response('no vault', { status: 503 });
const rel = decodeURIComponent(new URL(req.url).pathname).replace(/^\/+/, '');
const abs = nodePath.resolve(root, rel);
const relFromRoot = nodePath.relative(root, abs);
if (relFromRoot.startsWith('..') || nodePath.isAbsolute(relFromRoot)) return new Response('forbidden', { status: 403 });
try {
return await net.fetch(pathToFileURL(abs).toString());
} catch {
return new Response('not found', { status: 404 });
}
});
// 旧版数据迁移检测(FR-D1/D2/D3):幂等,无旧数据则跳过
const migrator = new MigrationService(app.getPath('userData'));
const oldDataDir = nodePath.join(app.getPath('userData'), 'legacy-data');