From 6be20f483cf3e0ca8c3a3dbc02812d7c9b965d0a Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 20 May 2025 23:37:37 +0800 Subject: [PATCH] feat: enhance shortcut management and add new commands - Removed the `$mod` replacement for `Meta` in shortcut handling to ensure proper cross-platform functionality. - Introduced new commands for "Open in Browser" and "Open Site in Browser" with corresponding shortcuts. - Updated the `useFeedActions` and `useListActions` hooks to utilize the new command shortcuts. - Improved the `transformShortcut` function for better platform-specific shortcut handling. Signed-off-by: Innei --- .../layer/renderer/src/atoms/context-menu.ts | 1 - .../layer/renderer/src/constants/shortcuts.ts | 4 ++ .../renderer/src/hooks/biz/useFeedActions.tsx | 51 ++++++++++++++----- .../src/modules/command/commands/id.ts | 2 + .../modules/command/commands/subscription.tsx | 28 ++++++++++ .../command/hooks/use-command-binding.ts | 8 ++- locales/shortcuts/en.json | 1 + packages/internal/utils/src/utils.ts | 6 +-- 8 files changed, 82 insertions(+), 19 deletions(-) diff --git a/apps/desktop/layer/renderer/src/atoms/context-menu.ts b/apps/desktop/layer/renderer/src/atoms/context-menu.ts index f8344fa65..bc943d0d5 100644 --- a/apps/desktop/layer/renderer/src/atoms/context-menu.ts +++ b/apps/desktop/layer/renderer/src/atoms/context-menu.ts @@ -56,7 +56,6 @@ export type FollowMenuItem = MenuItemText | MenuItemSeparator export type MenuItemInput = MenuItemText | MenuItemSeparator | NilValue function sortShortcutsString(shortcut: string) { - shortcut = shortcut.replace("$mod", "Meta") const order = ["Shift", "Ctrl", "Meta", "Alt"] const nextShortcut = transformShortcut(shortcut) diff --git a/apps/desktop/layer/renderer/src/constants/shortcuts.ts b/apps/desktop/layer/renderer/src/constants/shortcuts.ts index c35e160ec..cbeee4397 100644 --- a/apps/desktop/layer/renderer/src/constants/shortcuts.ts +++ b/apps/desktop/layer/renderer/src/constants/shortcuts.ts @@ -41,6 +41,10 @@ const shortcutConfigs = { name: tShortcuts("keys.subscriptions.openInBrowser"), key: "O", }, + openSiteInBrowser: { + name: tShortcuts("keys.subscriptions.openSiteInBrowser"), + key: "$mod+O", + }, markAllAsRead: { name: tShortcuts("keys.entries.markAllAsRead"), key: "Shift+$mod+A", diff --git a/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx b/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx index dbff5df33..1865ab042 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx @@ -13,11 +13,12 @@ import { MenuItemSeparator, MenuItemText } from "~/atoms/context-menu" import { useIsInMASReview } from "~/atoms/server-configs" import { whoami } from "~/atoms/user" import { useModalStack } from "~/components/ui/modal/stacked/hooks" -import { shortcuts } from "~/constants/shortcuts" import { apiClient } from "~/lib/api-fetch" import { UrlBuilder } from "~/lib/url-builder" import { useBoostModal } from "~/modules/boost/hooks" import { useFeedClaimModal } from "~/modules/claim" +import { COMMAND_ID } from "~/modules/command/commands/id" +import { useCommandShortcuts } from "~/modules/command/hooks/use-command-binding" import { FeedForm } from "~/modules/discover/FeedForm" import { InboxForm } from "~/modules/discover/InboxForm" import { ListForm } from "~/modules/discover/ListForm" @@ -46,11 +47,11 @@ const ConfirmDestroyModalContent = ({ onConfirm }: { onConfirm: () => void }) => return (
- + {t("sidebar.feed_actions.unfollow_feed_many_warning")}
-
@@ -107,6 +108,8 @@ export const useFeedActions = ({ const isInMASReview = useIsInMASReview() + const shortcuts = useCommandShortcuts() + const items = useMemo(() => { const related = feed || inbox if (!related) return [] @@ -116,7 +119,7 @@ export const useFeedActions = ({ const items: MenuItemInput[] = [ new MenuItemText({ label: t("sidebar.feed_actions.mark_all_as_read"), - shortcut: "$mod+Shift+A", + shortcut: shortcuts[COMMAND_ID.subscription.markAllAsRead], disabled: isEntryList, click: () => subscriptionActions.markReadByFeedIds({ @@ -138,8 +141,10 @@ export const useFeedActions = ({ }), ...(isFeedOwner ? [ + MenuItemSeparator.default, new MenuItemText({ label: t("sidebar.feed_actions.feed_owned_by_you"), + disabled: true, }), new MenuItemText({ label: t("sidebar.feed_actions.reset_feed"), @@ -147,6 +152,7 @@ export const useFeedActions = ({ resetFeed(feedId) }, }), + MenuItemSeparator.default, ] : []), ...(!isInMASReview @@ -294,7 +300,7 @@ export const useFeedActions = ({ new MenuItemText({ label: t("sidebar.feed_actions.navigate_to_feed"), shortcut: "$mod+G", - disabled: isInbox || !isEntryList || getRouteParams().feedId === feedId, + disabled: getRouteParams().feedId === feedId, click: () => { navigateEntry({ feedId }) }, @@ -305,14 +311,14 @@ export const useFeedActions = ({ which: t(IN_ELECTRON ? "words.browser" : "words.newTab"), }), disabled: isEntryList, - shortcut: "O", + shortcut: shortcuts[COMMAND_ID.subscription.openInBrowser], click: () => window.open(UrlBuilder.shareFeed(feedId, view), "_blank"), }), new MenuItemText({ label: t("sidebar.feed_actions.open_site_in_browser", { which: t(IN_ELECTRON ? "words.browser" : "words.newTab"), }), - shortcut: "$mod+O", + shortcut: shortcuts[COMMAND_ID.subscription.openSiteInBrowser], disabled: isEntryList, click: () => { const feed = getFeedById(feedId) @@ -364,7 +370,9 @@ export const useFeedActions = ({ feed, inbox, t, + shortcuts, isEntryList, + isInMASReview, isInbox, listByView, categories, @@ -396,18 +404,33 @@ export const useListActions = ({ listId, view }: { listId: string; view?: FeedVi const { present } = useModalStack() const { mutateAsync: deleteSubscription } = useDeleteSubscription({}) + const shortcuts = useCommandShortcuts() const navigateEntry = useNavigateEntry() const items = useMemo(() => { if (!list) return [] const items: MenuItemInput[] = [ - list.ownerUserId === whoami()?.id && - new MenuItemText({ - label: t("sidebar.feed_actions.list_owned_by_you"), - }), - list.ownerUserId !== whoami()?.id && MenuItemSeparator.default, + ...(list.ownerUserId === whoami()?.id + ? [ + new MenuItemText({ + label: t("sidebar.feed_actions.list_owned_by_you"), + disabled: true, + }), + MenuItemSeparator.default, + ] + : []), + new MenuItemText({ + label: t("sidebar.feed_actions.mark_all_as_read"), + shortcut: shortcuts[COMMAND_ID.subscription.markAllAsRead], + click: () => { + subscriptionActions.markReadByFeedIds({ + feedIds: list.feedIds, + }) + }, + }), + MenuItemSeparator.default, new MenuItemText({ label: t("sidebar.feed_actions.edit"), shortcut: "E", @@ -436,7 +459,7 @@ export const useListActions = ({ listId, view }: { listId: string; view?: FeedVi label: t("sidebar.feed_actions.open_list_in_browser", { which: t(IN_ELECTRON ? "words.browser" : "words.newTab"), }), - shortcut: shortcuts.subscriptions.openInBrowser.key, + shortcut: shortcuts[COMMAND_ID.subscription.openInBrowser], click: () => window.open(UrlBuilder.shareList(listId, view), "_blank"), }), MenuItemSeparator.default, @@ -457,7 +480,7 @@ export const useListActions = ({ listId, view }: { listId: string; view?: FeedVi ] return items - }, [list, t, present, deleteSubscription, subscription, navigateEntry, listId, view]) + }, [list, t, shortcuts, listId, present, deleteSubscription, subscription, navigateEntry, view]) return items } diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/id.ts b/apps/desktop/layer/renderer/src/modules/command/commands/id.ts index 89119adb6..842b51379 100644 --- a/apps/desktop/layer/renderer/src/modules/command/commands/id.ts +++ b/apps/desktop/layer/renderer/src/modules/command/commands/id.ts @@ -77,5 +77,7 @@ export const COMMAND_ID = { toggleFolderCollapse: "subscription:toggle-folder-collapse", markAllAsRead: "subscription:mark-all-as-read", + openInBrowser: "subscription:open-in-browser", + openSiteInBrowser: "subscription:open-site-in-browser", }, } as const diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/subscription.tsx b/apps/desktop/layer/renderer/src/modules/command/commands/subscription.tsx index 371a7c1b5..f8088d19d 100644 --- a/apps/desktop/layer/renderer/src/modules/command/commands/subscription.tsx +++ b/apps/desktop/layer/renderer/src/modules/command/commands/subscription.tsx @@ -22,6 +22,8 @@ declare module "@follow/utils/event-bus" { "subscription:previous": never "subscription:toggle-folder-collapse": never "subscription:mark-all-as-read": BizRouteParams + "subscription:open-in-browser": never + "subscription:open-site-in-browser": never } } const LABEL_PREFIX = "Subscription" @@ -112,6 +114,20 @@ export const useRegisterSubscriptionCommands = () => { EventBus.dispatch(COMMAND_ID.subscription.markAllAsRead, routeParams) }, }, + { + id: COMMAND_ID.subscription.openInBrowser, + label: `${LABEL_PREFIX}: Open in Browser`, + run: () => { + EventBus.dispatch(COMMAND_ID.subscription.openInBrowser) + }, + }, + { + id: COMMAND_ID.subscription.openSiteInBrowser, + label: `${LABEL_PREFIX}: Open site in Browser`, + run: () => { + EventBus.dispatch(COMMAND_ID.subscription.openSiteInBrowser) + }, + }, ]) } @@ -175,6 +191,16 @@ type MarkAllAsReadCommand = Command<{ fn: () => void }> +type OpenInBrowserCommand = Command<{ + id: typeof COMMAND_ID.subscription.openInBrowser + fn: () => void +}> + +type OpenSiteInBrowserCommand = Command<{ + id: typeof COMMAND_ID.subscription.openSiteInBrowser + fn: () => void +}> + export type SubscriptionCommand = | SwitchTabToNextCommand | SwitchTabToPreviousCommand @@ -188,3 +214,5 @@ export type SubscriptionCommand = | PreviousSubscriptionCommand | ToggleFolderCollapseCommand | MarkAllAsReadCommand + | OpenInBrowserCommand + | OpenSiteInBrowserCommand diff --git a/apps/desktop/layer/renderer/src/modules/command/hooks/use-command-binding.ts b/apps/desktop/layer/renderer/src/modules/command/hooks/use-command-binding.ts index 14a8cab53..6d3c9f70a 100644 --- a/apps/desktop/layer/renderer/src/modules/command/hooks/use-command-binding.ts +++ b/apps/desktop/layer/renderer/src/modules/command/hooks/use-command-binding.ts @@ -35,17 +35,23 @@ const defaultCommandShortcuts = { [COMMAND_ID.subscription.toggleFolderCollapse]: shortcuts.subscriptions.toggleFolderCollapse.key, [COMMAND_ID.subscription.markAllAsRead]: shortcuts.subscriptions.markAllAsRead.key, + [COMMAND_ID.subscription.openInBrowser]: shortcuts.subscriptions.openInBrowser.key, + [COMMAND_ID.subscription.openSiteInBrowser]: shortcuts.subscriptions.openSiteInBrowser.key, } as const export type BindingCommandId = keyof typeof defaultCommandShortcuts // eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-prefix, @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks -export const useCommandShortcut = (commandId: BindingCommandId): string => { +const useCommandShortcut = (commandId: BindingCommandId): string => { const commandShortcut = defaultCommandShortcuts[commandId] return commandShortcut } +export const useCommandShortcuts = () => { + return defaultCommandShortcuts +} + export const useCommandBinding = ({ commandId, when = true, diff --git a/locales/shortcuts/en.json b/locales/shortcuts/en.json index 197a5f652..3ab296943 100644 --- a/locales/shortcuts/en.json +++ b/locales/shortcuts/en.json @@ -24,6 +24,7 @@ "keys.subscriptions.add": "Add Subscription", "keys.subscriptions.nextSubscription": "Next Subscription", "keys.subscriptions.openInBrowser": "Open in Browser", + "keys.subscriptions.openSiteInBrowser": "Open site in Browser", "keys.subscriptions.previousSubscription": "Previous Subscription", "keys.subscriptions.switchBetweenViews": "Switch Between Views", "keys.subscriptions.switchNextView": "Switch to Next View", diff --git a/packages/internal/utils/src/utils.ts b/packages/internal/utils/src/utils.ts index 6e87c59a2..d22edbd4e 100644 --- a/packages/internal/utils/src/utils.ts +++ b/packages/internal/utils/src/utils.ts @@ -345,10 +345,10 @@ export const toScientificNotation = ( } export function transformShortcut(shortcut: string, platform: OS = getOS()): string { - if (platform === "Windows") { - return shortcut.replace("Meta", "Ctrl").replace("meta", "ctrl") + if (platform === "macOS") { + return shortcut.replace("$mod", "Meta") } - return shortcut + return shortcut.replace("$mod", "Ctrl") } // time like 1:30:00