From 336b65a887126f60262ae755fe3cb141a791437d Mon Sep 17 00:00:00 2001 From: DanielSong Date: Sat, 9 May 2026 12:25:47 +0800 Subject: [PATCH] feat(titlebar): add current file actions --- src/main/ipcBridge.ts | 25 ++ src/preload.ts | 3 + src/renderer/components/menu/MenuDropDown.vue | 99 ++++--- src/renderer/components/menu/TitleBar.vue | 272 ++++++++++++++++-- src/renderer/components/workspace/TabBar.vue | 96 ++++++- src/renderer/global.d.ts | 2 + 6 files changed, 430 insertions(+), 67 deletions(-) diff --git a/src/main/ipcBridge.ts b/src/main/ipcBridge.ts index 8f993b7..dfb9893 100644 --- a/src/main/ipcBridge.ts +++ b/src/main/ipcBridge.ts @@ -593,6 +593,27 @@ export function registerIpcOnHandlers() { await shell.openExternal(externalUrl); } }); + ipcMain.handle("shell:revealFileInFolder", async (_event, filePath: string) => { + if (!filePath) return false; + + try { + const normalizedPath = path.normalize(filePath); + if (fs.existsSync(normalizedPath)) { + shell.showItemInFolder(normalizedPath); + return true; + } + + const directoryPath = path.dirname(normalizedPath); + if (!fs.existsSync(directoryPath)) { + return false; + } + + const openResult = await shell.openPath(directoryPath); + return openResult === ""; + } catch { + return false; + } + }); ipcMain.on("change-save-status", (event, isSavedStatus) => { const targetWin = BrowserWindow.fromWebContents(event.sender); if (!targetWin) return; @@ -1050,6 +1071,10 @@ export function registerIpcHandleHandlers() { } // 无需 win 的 ipc 处理 export function registerGlobalIpcHandlers() { + ipcMain.handle("clipboard:writeText", async (_event, text: string): Promise => { + clipboard.writeText(text ?? ""); + return true; + }); ipcMain.handle( "image:openPreview", async (event, payload: ImagePreviewPayload): Promise => { diff --git a/src/preload.ts b/src/preload.ts index 189896c..2bc14cc 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -62,12 +62,15 @@ contextBridge.exposeInMainWorld("electronAPI", { openExternal: (url: string) => ipcRenderer.send("shell:openExternal", url), openLink: (href: string, currentFilePath?: string | null) => ipcRenderer.invoke("shell:openLink", href, currentFilePath), + revealFileInFolder: (filePath: string) => + ipcRenderer.invoke("shell:revealFileInFolder", filePath), openImagePreview: ( src: string, alt?: string, options?: { items?: Array<{ src: string; alt?: string }>; index?: number } ) => ipcRenderer.invoke("image:openPreview", { src, alt, ...options }), getFilePathInClipboard: () => ipcRenderer.invoke("clipboard:getFilePath"), + writeTextToClipboard: (text: string) => ipcRenderer.invoke("clipboard:writeText", text), writeTempImage: ( file: Uint8Array, targetPath: string, diff --git a/src/renderer/components/menu/MenuDropDown.vue b/src/renderer/components/menu/MenuDropDown.vue index 69ce06b..47337bb 100644 --- a/src/renderer/components/menu/MenuDropDown.vue +++ b/src/renderer/components/menu/MenuDropDown.vue @@ -4,15 +4,18 @@ import emitter from "@/renderer/events"; import MenuBar from "./MenuBar.vue"; const isOpen = ref(false); +function closeMenu() { + isOpen.value = false; +} // 事件处理器 function handleFileChange() { - isOpen.value = false; + closeMenu(); } function handleKeydown(event: KeyboardEvent) { if (event.key === "Escape" && isOpen.value) { - isOpen.value = false; + closeMenu(); } } @@ -29,11 +32,10 @@ onUnmounted(() => { diff --git a/src/renderer/components/menu/TitleBar.vue b/src/renderer/components/menu/TitleBar.vue index f140a4d..4c6060c 100644 --- a/src/renderer/components/menu/TitleBar.vue +++ b/src/renderer/components/menu/TitleBar.vue @@ -1,10 +1,13 @@