fix(desktop): preserve native image context menu

This commit is contained in:
DIYgod 2026-05-14 18:55:17 +08:00
parent 444f8e05a5
commit 431ab0e0be
10 changed files with 65 additions and 65 deletions

View File

@ -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<any> {
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<any> {
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()

View File

@ -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

View File

@ -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",

View File

@ -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
? []

View File

@ -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<string>,
): NonNullable<MarkdownRenderActions["onImageContextMenu"]> => {
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: <SimpleIconsEagle />,
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.
})
}

View File

@ -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",

View File

@ -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",

View File

@ -14,6 +14,7 @@
"contextMenu.saveImage": "画像を保存",
"contextMenu.saveImageAs": "画像を名前を付けて保存...",
"contextMenu.saveLinkAs": "名前をつけてリンクを保存...",
"contextMenu.saveMediaToEagle": "メディアを Eagle に保存",
"contextMenu.saveVideo": "動画を保存",
"contextMenu.saveVideoAs": "動画を名前を付けて保存...",
"contextMenu.searchWithGoogle": "Google で検索",

View File

@ -14,6 +14,7 @@
"contextMenu.saveImage": "保存图片",
"contextMenu.saveImageAs": "图片另存为...",
"contextMenu.saveLinkAs": "链接另存为...",
"contextMenu.saveMediaToEagle": "保存到 Eagle",
"contextMenu.saveVideo": "保存视频",
"contextMenu.saveVideoAs": "视频另存为...",
"contextMenu.searchWithGoogle": "在谷歌中搜索",

View File

@ -14,6 +14,7 @@
"contextMenu.saveImage": "儲存圖片",
"contextMenu.saveImageAs": "圖片另存為...",
"contextMenu.saveLinkAs": "連結另存為...",
"contextMenu.saveMediaToEagle": "儲存媒體至 Eagle",
"contextMenu.saveVideo": "儲存影片",
"contextMenu.saveVideoAs": "影片另存為...",
"contextMenu.searchWithGoogle": "使用 Google 搜尋",