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
@@ -119,6 +119,24 @@ export class FileService {
return destRel;
}
// FR-E6:导入本地图片到 .attachments,返回库内相对路径(POSIX)
async importAsset(srcAbs: string): Promise<string> {
await fs.mkdir(nodePath.join(this.root, ATTACHMENTS_DIR), { recursive: true });
const finalName = await dedupeName(this.root, ATTACHMENTS_DIR, sanitizeName(nodePath.basename(srcAbs)));
const destRel = joinRel(ATTACHMENTS_DIR, finalName);
await fs.copyFile(srcAbs, this.abs(destRel));
return destRel;
}
// FR-E6:写入图片字节(base64)到 .attachments,返回库内相对路径(剪贴板粘贴入库)
async writeAsset(name: string, dataBase64: string): Promise<string> {
await fs.mkdir(nodePath.join(this.root, ATTACHMENTS_DIR), { recursive: true });
const finalName = await dedupeName(this.root, ATTACHMENTS_DIR, sanitizeName(name));
const destRel = joinRel(ATTACHMENTS_DIR, finalName);
await fs.writeFile(this.abs(destRel), Buffer.from(dataBase64, 'base64'));
return destRel;
}
async readDoc(path: string): Promise<ReadDocResult> {
const abs = this.abs(path);
const raw = await fs.readFile(abs, 'utf8');
@@ -46,6 +46,17 @@ export class VaultService {
return result.filePaths[0];
}
// FR-E6:选择本地图片并导入 .attachments,返回库内相对路径(null=取消)
async pickImage(): Promise<string | null> {
if (!this.fileService) throw new Error('vault not initialized');
const r = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: '图片', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico'] }],
});
if (r.canceled || r.filePaths.length === 0) return null;
return this.fileService.importAsset(r.filePaths[0]);
}
async switch(root: string): Promise<VaultMeta> {
// 校验是存在的目录
const stat = await fs.stat(root).catch(() => null);