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 <tukon479@gmail.com>
This commit is contained in:
Innei 2025-05-20 23:37:37 +08:00
parent 0f5a0ad8b0
commit 6be20f483c
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
8 changed files with 82 additions and 19 deletions

View File

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

View File

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

View File

@ -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 (
<div className="w-[540px]">
<div className="mb-4">
<i className="i-mingcute-warning-fill -mb-1 mr-1 size-5 text-red-500" />
<i className="i-mingcute-warning-fill text-red -mb-1 mr-1 size-5" />
{t("sidebar.feed_actions.unfollow_feed_many_warning")}
</div>
<div className="flex justify-end">
<Button buttonClassName="bg-red-600" onClick={onConfirm}>
<Button buttonClassName="bg-red" onClick={onConfirm}>
{t("words.confirm")}
</Button>
</div>
@ -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
}

View File

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

View File

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

View File

@ -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 = <T extends BindingCommandId>({
commandId,
when = true,

View File

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

View File

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