diff --git a/apps/desktop/layer/main/src/ipc/services/integration.ts b/apps/desktop/layer/main/src/ipc/services/integration.ts index a44a52aec..539599f82 100644 --- a/apps/desktop/layer/main/src/ipc/services/integration.ts +++ b/apps/desktop/layer/main/src/ipc/services/integration.ts @@ -32,6 +32,10 @@ interface SaveToEagleInput { mediaUrls: string[] } +interface SetEagleContextMenuEnabledInput { + enabled: boolean +} + interface LoginToQBittorrentInput { host: string username: string @@ -55,6 +59,29 @@ interface CustomFetchInput { timeout?: number } +export async function saveMediaToEagle(input: SaveToEagleInput): Promise { + try { + const res = await fetch("http://localhost:41595/api/item/addFromURLs", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + items: input.mediaUrls?.map((media) => ({ + url: media, + website: input.url, + headers: { + referer: input.url, + }, + })), + }), + }) + return await res.json() + } catch { + return null + } +} + export class IntegrationService extends IpcService { static override readonly groupName = "integration" @@ -125,26 +152,12 @@ ${content} @IpcMethod() async saveToEagle(context: IpcContext, input: SaveToEagleInput): Promise { - try { - const res = await fetch("http://localhost:41595/api/item/addFromURLs", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - items: input.mediaUrls?.map((media) => ({ - url: media, - website: input.url, - headers: { - referer: input.url, - }, - })), - }), - }) - return await res.json() - } catch { - return null - } + return saveMediaToEagle(input) + } + + @IpcMethod() + setEagleContextMenuEnabled(context: IpcContext, input: SetEagleContextMenuEnabledInput): void { + store.set("eagleContextMenuEnabled", input.enabled) } @IpcMethod() diff --git a/apps/desktop/layer/main/src/lib/store.ts b/apps/desktop/layer/main/src/lib/store.ts index defb6d2d4..de4710595 100644 --- a/apps/desktop/layer/main/src/lib/store.ts +++ b/apps/desktop/layer/main/src/lib/store.ts @@ -7,6 +7,7 @@ type StoreData = { "notifications-persistent-ids"?: string[] | null appearance?: "light" | "dark" | "system" | null cacheSizeLimit?: number | null + eagleContextMenuEnabled?: boolean | null minimizeToTray?: boolean | null proxy?: string | null qbittorrentSID?: string | null diff --git a/apps/desktop/layer/main/src/manager/app.ts b/apps/desktop/layer/main/src/manager/app.ts index c9691e3db..8a99787e7 100644 --- a/apps/desktop/layer/main/src/manager/app.ts +++ b/apps/desktop/layer/main/src/manager/app.ts @@ -10,6 +10,7 @@ import { WindowManager } from "~/manager/window" import { getIconPath } from "../helper" import { initializeIpcServices } from "../ipc" +import { saveMediaToEagle } from "../ipc/services/integration" import { checkAndCleanCodeCache, clearCacheCronJob } from "../lib/cleaner" import { getSessionTokenFromCookies, syncSessionToCliConfig } from "../lib/cli-session-sync" import { t } from "../lib/i18n" @@ -213,6 +214,19 @@ class AppManagerStatic { prepend: (_defaultActions, params) => { return [ + { + label: t("contextMenu.saveMediaToEagle"), + visible: + params.mediaType === "image" && + params.srcURL !== "" && + !!store.get("eagleContextMenuEnabled"), + click: () => { + void saveMediaToEagle({ + url: params.pageURL || params.srcURL, + mediaUrls: [params.srcURL], + }) + }, + }, { label: t("contextMenu.openImageInBrowser"), visible: params.mediaType === "image", diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx b/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx index 68da40f0f..5879259f9 100644 --- a/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx +++ b/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx @@ -17,7 +17,7 @@ import { tracker } from "@follow/tracker" import { useMutation, useQuery } from "@tanstack/react-query" import type { FetchError } from "ofetch" import { ofetch } from "ofetch" -import { useMemo } from "react" +import { useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" @@ -72,6 +72,14 @@ const useRegisterEagleCommands = () => { const isEagleAvailable = enableEagle && (checkEagle.isLoading ? false : !!checkEagle.data) + useEffect(() => { + if (!IN_ELECTRON) return + + void ipcServices?.integration.setEagleContextMenuEnabled({ + enabled: isEagleAvailable, + }) + }, [isEagleAvailable]) + useRegisterCommandEffect( !isEagleAvailable ? [] diff --git a/apps/desktop/layer/renderer/src/modules/renderer/hooks/useImageContextMenu.tsx b/apps/desktop/layer/renderer/src/modules/renderer/hooks/useImageContextMenu.tsx index 94d2e0ee2..c6803c834 100644 --- a/apps/desktop/layer/renderer/src/modules/renderer/hooks/useImageContextMenu.tsx +++ b/apps/desktop/layer/renderer/src/modules/renderer/hooks/useImageContextMenu.tsx @@ -1,54 +1,13 @@ -import { SimpleIconsEagle } from "@follow/components/ui/platform-icon/icons.js" -import { IN_ELECTRON } from "@follow/shared/constants" -import { useTranslation } from "react-i18next" -import { toast } from "sonner" import { useEventCallback } from "usehooks-ts" -import { MenuItemText, useShowContextMenu } from "~/atoms/context-menu" -import { useIntegrationSettingKey } from "~/atoms/settings/integration" -import { ipcServices } from "~/lib/client" - import type { MarkdownRenderActions } from "../../../components/ui/markdown/types" export const useImageContextMenu = ( entryUrl?: Nullable, ): NonNullable => { - const { t } = useTranslation() - const showContextMenu = useShowContextMenu() - const enableEagle = useIntegrationSettingKey("enableEagle") + void entryUrl - return useEventCallback(async (event, imageUrl) => { - if (!IN_ELECTRON || !enableEagle || !ipcServices?.integration?.saveToEagle) { - return - } - - event.preventDefault() - event.stopPropagation() - - await showContextMenu( - [ - new MenuItemText({ - label: t("entry_actions.save_image_to_eagle"), - icon: , - click: async () => { - const response = await ipcServices?.integration.saveToEagle({ - url: entryUrl || imageUrl, - mediaUrls: [imageUrl], - }) - - if (response?.status === "success") { - toast.success(t("entry_actions.saved_to_eagle"), { - duration: 3000, - }) - } else { - toast.error(t("entry_actions.failed_to_save_to_eagle"), { - duration: 3000, - }) - } - }, - }), - ], - event, - ) + return useEventCallback(() => { + // Keep Chromium's native image context menu. Electron appends custom media actions in main. }) } diff --git a/locales/native/en.json b/locales/native/en.json index 184442b56..6951d302b 100644 --- a/locales/native/en.json +++ b/locales/native/en.json @@ -14,6 +14,7 @@ "contextMenu.saveImage": "Save Image", "contextMenu.saveImageAs": "Save Image As...", "contextMenu.saveLinkAs": "Save Link As...", + "contextMenu.saveMediaToEagle": "Save Media to Eagle", "contextMenu.saveVideo": "Save Video", "contextMenu.saveVideoAs": "Save Video As...", "contextMenu.searchWithGoogle": "Search with Google", diff --git a/locales/native/fr-FR.json b/locales/native/fr-FR.json index d50882a05..1a18c3540 100644 --- a/locales/native/fr-FR.json +++ b/locales/native/fr-FR.json @@ -14,6 +14,7 @@ "contextMenu.saveImage": "Enregistrer l'image", "contextMenu.saveImageAs": "Enregistrer l'image sous...", "contextMenu.saveLinkAs": "Enregistrer le lien sous...", + "contextMenu.saveMediaToEagle": "Enregistrer le média dans Eagle", "contextMenu.saveVideo": "Enregistrer la vidéo", "contextMenu.saveVideoAs": "Enregistrer la vidéo sous...", "contextMenu.searchWithGoogle": "Rechercher avec Google", diff --git a/locales/native/ja.json b/locales/native/ja.json index 0ba700298..334257899 100644 --- a/locales/native/ja.json +++ b/locales/native/ja.json @@ -14,6 +14,7 @@ "contextMenu.saveImage": "画像を保存", "contextMenu.saveImageAs": "画像を名前を付けて保存...", "contextMenu.saveLinkAs": "名前をつけてリンクを保存...", + "contextMenu.saveMediaToEagle": "メディアを Eagle に保存", "contextMenu.saveVideo": "動画を保存", "contextMenu.saveVideoAs": "動画を名前を付けて保存...", "contextMenu.searchWithGoogle": "Google で検索", diff --git a/locales/native/zh-CN.json b/locales/native/zh-CN.json index c6baaeccd..fe8db11ee 100644 --- a/locales/native/zh-CN.json +++ b/locales/native/zh-CN.json @@ -14,6 +14,7 @@ "contextMenu.saveImage": "保存图片", "contextMenu.saveImageAs": "图片另存为...", "contextMenu.saveLinkAs": "链接另存为...", + "contextMenu.saveMediaToEagle": "保存到 Eagle", "contextMenu.saveVideo": "保存视频", "contextMenu.saveVideoAs": "视频另存为...", "contextMenu.searchWithGoogle": "在谷歌中搜索", diff --git a/locales/native/zh-TW.json b/locales/native/zh-TW.json index cd711afab..0e83952da 100644 --- a/locales/native/zh-TW.json +++ b/locales/native/zh-TW.json @@ -14,6 +14,7 @@ "contextMenu.saveImage": "儲存圖片", "contextMenu.saveImageAs": "圖片另存為...", "contextMenu.saveLinkAs": "連結另存為...", + "contextMenu.saveMediaToEagle": "儲存媒體至 Eagle", "contextMenu.saveVideo": "儲存影片", "contextMenu.saveVideoAs": "影片另存為...", "contextMenu.searchWithGoogle": "使用 Google 搜尋",