feat: refactor zen mode functionality and update shortcuts
- Removed the zen mode option from the app menu and adjusted the corresponding global shortcut to use `$mod+Shift+Z`. - Updated the zen mode state management in the settings module to streamline the toggle functionality. - Enhanced command handling for zen mode, integrating it with the new command structure for better consistency. - Updated localization files to reflect changes in shortcut labels. This refactor improves the overall user experience by ensuring a more intuitive shortcut for toggling zen mode. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
96cb362944
commit
65b648be8f
|
|
@ -135,18 +135,7 @@ export const registerAppMenu = () => {
|
|||
{ role: "zoomIn", label: t("menu.zoomIn") },
|
||||
{ role: "zoomOut", label: t("menu.zoomOut") },
|
||||
{ type: "separator" },
|
||||
{
|
||||
type: "normal",
|
||||
label: t("menu.zenMode"),
|
||||
accelerator: "Ctrl+Shift+Z",
|
||||
click: () => {
|
||||
const mainWindow = getMainWindow()
|
||||
if (!mainWindow) return
|
||||
|
||||
const caller = callWindowExpose(mainWindow)
|
||||
caller.zenMode()
|
||||
},
|
||||
},
|
||||
{ role: "togglefullscreen", label: t("menu.toggleFullScreen") },
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { defaultUISettings } from "@follow/shared/settings/defaults"
|
|||
import { enhancedUISettingKeys } from "@follow/shared/settings/enhanced"
|
||||
import type { UISettings } from "@follow/shared/settings/interface"
|
||||
import { jotaiStore } from "@follow/utils/jotai"
|
||||
import { atom, useAtomValue, useSetAtom } from "jotai"
|
||||
import { atom, useAtomValue } from "jotai"
|
||||
import { useEventCallback } from "usehooks-ts"
|
||||
|
||||
import { getDefaultLanguage } from "~/lib/language"
|
||||
|
|
@ -69,10 +69,10 @@ export const useIsZenMode = () => useAtomValue(zenModeAtom)
|
|||
export const getIsZenMode = () => jotaiStore.get(zenModeAtom)
|
||||
|
||||
export const useSetZenMode = () => {
|
||||
const setZenMode = useSetAtom(zenModeAtom)
|
||||
return useEventCallback((checked: boolean) => {
|
||||
setZenMode(checked)
|
||||
})
|
||||
return setZenMode
|
||||
}
|
||||
export const setZenMode = (checked: boolean) => {
|
||||
jotaiStore.set(zenModeAtom, checked)
|
||||
}
|
||||
|
||||
export const useToggleZenMode = () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Divider } from "@follow/components/ui/divider/Divider.js"
|
||||
import { Kbd } from "@follow/components/ui/kbd/Kbd.js"
|
||||
import { RootPortal } from "@follow/components/ui/portal/index.js"
|
||||
import { useTypeScriptHappyCallback } from "@follow/hooks"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
|
|
@ -116,12 +117,14 @@ const DropdownMenuItem = ({
|
|||
icon,
|
||||
active,
|
||||
highlightColor = "accent",
|
||||
shortcut,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
icon?: React.ReactNode | ((props?: { isActive?: boolean }) => React.ReactNode)
|
||||
active?: boolean
|
||||
highlightColor?: "accent" | "gray"
|
||||
shortcut?: string
|
||||
} & { ref?: React.Ref<React.ElementRef<typeof DropdownMenuPrimitive.Item> | null> }) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
|
|
@ -144,8 +147,14 @@ const DropdownMenuItem = ({
|
|||
</span>
|
||||
)}
|
||||
{props.children}
|
||||
|
||||
{/* Justify Fill */}
|
||||
{!!icon && <span className="ml-1.5 size-4" />}
|
||||
{!!shortcut && (
|
||||
<Kbd wrapButton={false} className="ml-auto">
|
||||
{shortcut}
|
||||
</Kbd>
|
||||
)}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
)
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
|
|
|||
|
|
@ -55,17 +55,14 @@ const shortcutConfigs = {
|
|||
name: tShortcuts("keys.layout.toggleSidebar"),
|
||||
key: "$mod+B, [",
|
||||
},
|
||||
showShortcuts: {
|
||||
name: tShortcuts("keys.layout.showShortcuts"),
|
||||
key: "?",
|
||||
},
|
||||
|
||||
toggleWideMode: {
|
||||
name: tShortcuts("keys.layout.toggleWideMode"),
|
||||
key: "$mod+[",
|
||||
},
|
||||
zenMode: {
|
||||
toggleZenMode: {
|
||||
name: tShortcuts("keys.layout.zenMode"),
|
||||
key: "Ctrl+Shift+Z",
|
||||
key: "$mod+Shift+Z",
|
||||
},
|
||||
},
|
||||
entries: {
|
||||
|
|
@ -149,6 +146,10 @@ const shortcutConfigs = {
|
|||
name: tShortcuts("keys.misc.quickSearch"),
|
||||
key: "$mod+K",
|
||||
},
|
||||
showShortcuts: {
|
||||
name: tShortcuts("keys.misc.showShortcuts"),
|
||||
key: "?",
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import {
|
|||
} from "~/atoms/readability"
|
||||
import { useShowSourceContent } from "~/atoms/source-content"
|
||||
import { useUserRole, whoami } from "~/atoms/user"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { tipcClient } from "~/lib/client"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { getCommand, useRunCommandFn } from "~/modules/command/hooks/use-command"
|
||||
import { useCommandShortcuts } from "~/modules/command/hooks/use-command-binding"
|
||||
import type { FollowCommandId } from "~/modules/command/types"
|
||||
import { useToolbarOrderMap } from "~/modules/customize-toolbar/hooks"
|
||||
import { useEntry } from "~/store/entry"
|
||||
|
|
@ -168,6 +168,8 @@ export const useEntryActions = ({
|
|||
|
||||
const userRole = useUserRole()
|
||||
|
||||
const shortcuts = useCommandShortcuts()
|
||||
|
||||
const actionConfigs: EntryActionItem[] = useMemo(() => {
|
||||
if (!hasEntry) return []
|
||||
|
||||
|
|
@ -218,27 +220,28 @@ export const useEntryActions = ({
|
|||
{ entryId, feedId: feed?.id, userId: feed?.ownerUserId },
|
||||
]),
|
||||
hide: isInbox || feed?.ownerUserId === whoami()?.id,
|
||||
shortcut: shortcuts.entry.tip.key,
|
||||
// shortcut: shortcuts.entry.tip.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.tip],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
id: COMMAND_ID.entry.star,
|
||||
onClick: runCmdFn(COMMAND_ID.entry.star, [{ entryId, view }]),
|
||||
active: !!entry?.collections,
|
||||
shortcut: shortcuts.entry.toggleStarred.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.star],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
id: COMMAND_ID.entry.copyTitle,
|
||||
onClick: runCmdFn(COMMAND_ID.entry.copyTitle, [{ entryId }]),
|
||||
shortcut: shortcuts.entry.copyTitle.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.copyTitle],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
id: COMMAND_ID.entry.copyLink,
|
||||
onClick: runCmdFn(COMMAND_ID.entry.copyLink, [{ entryId }]),
|
||||
hide: !entry?.entries.url,
|
||||
shortcut: shortcuts.entry.copyLink.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.copyLink],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
|
|
@ -295,7 +298,7 @@ export const useEntryActions = ({
|
|||
id: COMMAND_ID.entry.share,
|
||||
onClick: runCmdFn(COMMAND_ID.entry.share, [{ entryId }]),
|
||||
hide: !entry?.entries.url || !("share" in navigator || IN_ELECTRON),
|
||||
shortcut: shortcuts.entry.share.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.share],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
|
|
@ -308,7 +311,7 @@ export const useEntryActions = ({
|
|||
onClick: runCmdFn(COMMAND_ID.entry.read, [{ entryId }]),
|
||||
hide: !hasEntry || !!entry.collections || !!inList,
|
||||
active: !!entry?.read,
|
||||
shortcut: shortcuts.entry.toggleRead.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.read],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
|
|
@ -331,7 +334,7 @@ export const useEntryActions = ({
|
|||
{ entryId, entryContent: entry?.entries.content! },
|
||||
]),
|
||||
hide: !IN_ELECTRON || compact || !entry?.entries.content,
|
||||
shortcut: shortcuts.entry.tts.key,
|
||||
shortcut: shortcuts[COMMAND_ID.entry.tts],
|
||||
entryId,
|
||||
}),
|
||||
new EntryActionMenuItem({
|
||||
|
|
@ -364,32 +367,33 @@ export const useEntryActions = ({
|
|||
|
||||
return configs
|
||||
}, [
|
||||
compact,
|
||||
entry?.collections,
|
||||
entry?.entries.content,
|
||||
entry?.entries.publishedAt,
|
||||
entry?.entries.url,
|
||||
entry?.read,
|
||||
entry?.settings?.readability,
|
||||
entry?.view,
|
||||
hasEntry,
|
||||
runCmdFn,
|
||||
entryId,
|
||||
isEntryInReadability,
|
||||
feed?.id,
|
||||
feed?.ownerUserId,
|
||||
feed?.siteUrl,
|
||||
hasEntry,
|
||||
imageLength,
|
||||
inList,
|
||||
isInbox,
|
||||
shortcuts,
|
||||
view,
|
||||
entry?.collections,
|
||||
entry?.entries.url,
|
||||
entry?.entries.publishedAt,
|
||||
entry?.entries.content,
|
||||
entry?.view,
|
||||
entry?.read,
|
||||
entry?.settings?.readability,
|
||||
imageLength,
|
||||
isShowSourceContent,
|
||||
isShowAISummaryAuto,
|
||||
isShowAISummaryOnce,
|
||||
userRole,
|
||||
isShowAITranslationAuto,
|
||||
isShowAITranslationOnce,
|
||||
isShowSourceContent,
|
||||
inList,
|
||||
compact,
|
||||
isEntryInReadability,
|
||||
isContentContainsHTMLTags,
|
||||
runCmdFn,
|
||||
userRole,
|
||||
view,
|
||||
])
|
||||
|
||||
return actionConfigs
|
||||
|
|
|
|||
|
|
@ -18,19 +18,12 @@ const parseAccelerator = (
|
|||
}
|
||||
|
||||
export const registerAppGlobalShortcuts = () => {
|
||||
// @see src/main/menu.ts
|
||||
// @see layer/main/menu.ts
|
||||
const shortcuts: ShortcutDefinition[] = [
|
||||
{
|
||||
accelerator: "CmdOrCtrl+,",
|
||||
action: () => window.router.showSettings(),
|
||||
},
|
||||
{
|
||||
accelerator: "Ctrl+Shift+Z",
|
||||
action: () => {
|
||||
const caller = callWindowExposeRenderer()
|
||||
caller.zenMode()
|
||||
},
|
||||
},
|
||||
{
|
||||
accelerator: "CmdOrCtrl+T",
|
||||
action: () => {
|
||||
|
|
|
|||
|
|
@ -50,11 +50,14 @@ export const COMMAND_ID = {
|
|||
focusToTimeline: "layout:focus-to-timeline",
|
||||
focusToSubscription: "layout:focus-to-subscription",
|
||||
focusToEntryRender: "layout:focus-to-entry-render",
|
||||
toggleWideMode: "layout:toggle-wide-mode",
|
||||
toggleZenMode: "layout:toggle-zen-mode",
|
||||
},
|
||||
timeline: {
|
||||
switchToNext: "timeline:switch-to-next",
|
||||
switchToPrevious: "timeline:switch-to-previous",
|
||||
refetch: "timeline:refetch",
|
||||
unreadOnly: "timeline:unread-only",
|
||||
},
|
||||
entryRender: {
|
||||
scrollDown: "entry-render:scroll-down",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { EventBus } from "@follow/utils/event-bus"
|
||||
|
||||
import { getIsZenMode, getUISettings, setUISetting, setZenMode } from "~/atoms/settings/ui"
|
||||
import { setTimelineColumnShow } from "~/atoms/sidebar"
|
||||
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command"
|
||||
|
|
@ -44,6 +45,21 @@ export const useRegisterLayoutCommands = () => {
|
|||
EventBus.dispatch(COMMAND_ID.layout.focusToEntryRender)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.layout.toggleWideMode,
|
||||
label: "Toggle wide mode",
|
||||
run: () => {
|
||||
const { wideMode } = getUISettings()
|
||||
setUISetting("wideMode", !wideMode)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.layout.toggleZenMode,
|
||||
label: "Toggle zen mode",
|
||||
run: () => {
|
||||
setZenMode(!getIsZenMode())
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -65,8 +81,21 @@ export type FocusToEntryRenderCommand = Command<{
|
|||
id: typeof COMMAND_ID.layout.focusToEntryRender
|
||||
fn: () => void
|
||||
}>
|
||||
|
||||
export type ToggleWideModeCommand = Command<{
|
||||
id: typeof COMMAND_ID.layout.toggleWideMode
|
||||
fn: () => void
|
||||
}>
|
||||
|
||||
export type ToggleZenModeCommand = Command<{
|
||||
id: typeof COMMAND_ID.layout.toggleZenMode
|
||||
fn: () => void
|
||||
}>
|
||||
|
||||
export type LayoutCommand =
|
||||
| ToggleTimelineColumnCommand
|
||||
| FocusToTimelineCommand
|
||||
| FocusToSubscriptionCommand
|
||||
| FocusToEntryRenderCommand
|
||||
| ToggleWideModeCommand
|
||||
| ToggleZenModeCommand
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { EventBus } from "@follow/utils/event-bus"
|
||||
|
||||
import { setGeneralSetting } from "~/atoms/settings/general"
|
||||
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command"
|
||||
import type { Command } from "../types"
|
||||
import { COMMAND_ID } from "./id"
|
||||
|
|
@ -36,6 +38,13 @@ export const useRegisterTimelineCommand = () => {
|
|||
EventBus.dispatch("timeline:refetch")
|
||||
},
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.timeline.unreadOnly,
|
||||
label: "Unread Only",
|
||||
run: (unreadOnly: boolean) => {
|
||||
setGeneralSetting("unreadOnly", unreadOnly)
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +63,13 @@ export type RefetchTimelineCommand = Command<{
|
|||
fn: () => void
|
||||
}>
|
||||
|
||||
export type UnreadOnlyTimelineCommand = Command<{
|
||||
id: typeof COMMAND_ID.timeline.unreadOnly
|
||||
fn: (unreadOnly: boolean) => void
|
||||
}>
|
||||
|
||||
export type TimelineCommand =
|
||||
| SwitchToNextTimelineCommand
|
||||
| SwitchToPreviousTimelineCommand
|
||||
| RefetchTimelineCommand
|
||||
| UnreadOnlyTimelineCommand
|
||||
|
|
|
|||
|
|
@ -5,38 +5,45 @@ import type { RegisterHotkeyOptions } from "./use-register-hotkey"
|
|||
import { useCommandHotkey } from "./use-register-hotkey"
|
||||
|
||||
const defaultCommandShortcuts = {
|
||||
[COMMAND_ID.entry.read]: shortcuts.entry.toggleRead.key,
|
||||
[COMMAND_ID.entry.openInBrowser]: shortcuts.entry.openInBrowser.key,
|
||||
[COMMAND_ID.entry.star]: shortcuts.entry.toggleStarred.key,
|
||||
// Entry commands
|
||||
[COMMAND_ID.entry.copyLink]: shortcuts.entry.copyLink.key,
|
||||
[COMMAND_ID.entry.copyTitle]: shortcuts.entry.copyTitle.key,
|
||||
[COMMAND_ID.entry.tts]: shortcuts.entry.tts.key,
|
||||
[COMMAND_ID.entry.tip]: shortcuts.entry.tip.key,
|
||||
[COMMAND_ID.entry.openInBrowser]: shortcuts.entry.openInBrowser.key,
|
||||
[COMMAND_ID.entry.read]: shortcuts.entry.toggleRead.key,
|
||||
[COMMAND_ID.entry.share]: shortcuts.entry.share.key,
|
||||
[COMMAND_ID.entry.star]: shortcuts.entry.toggleStarred.key,
|
||||
[COMMAND_ID.entry.tip]: shortcuts.entry.tip.key,
|
||||
[COMMAND_ID.entry.tts]: shortcuts.entry.tts.key,
|
||||
|
||||
[COMMAND_ID.entryRender.scrollUp]: shortcuts.entry.scrollUp.key,
|
||||
// Entry render commands
|
||||
[COMMAND_ID.entryRender.nextEntry]: shortcuts.entry.nextEntry.key,
|
||||
[COMMAND_ID.entryRender.previousEntry]: shortcuts.entry.previousEntry.key,
|
||||
[COMMAND_ID.entryRender.scrollDown]: shortcuts.entry.scrollDown.key,
|
||||
[COMMAND_ID.entryRender.scrollUp]: shortcuts.entry.scrollUp.key,
|
||||
|
||||
[COMMAND_ID.timeline.switchToNext]: shortcuts.entries.next.key,
|
||||
[COMMAND_ID.timeline.switchToPrevious]: shortcuts.entries.previous.key,
|
||||
[COMMAND_ID.timeline.refetch]: shortcuts.entries.refetch.key,
|
||||
// Global commands
|
||||
[COMMAND_ID.global.showShortcuts]: shortcuts.misc.showShortcuts.key,
|
||||
|
||||
// Layout commands
|
||||
[COMMAND_ID.layout.toggleTimelineColumn]: shortcuts.layout.toggleSidebar.key,
|
||||
[COMMAND_ID.layout.toggleWideMode]: shortcuts.layout.toggleWideMode.key,
|
||||
[COMMAND_ID.layout.toggleZenMode]: shortcuts.layout.toggleZenMode.key,
|
||||
|
||||
// Subscription commands
|
||||
[COMMAND_ID.subscription.markAllAsRead]: shortcuts.subscriptions.markAllAsRead.key,
|
||||
[COMMAND_ID.subscription.nextSubscription]: shortcuts.subscriptions.nextSubscription.key,
|
||||
[COMMAND_ID.subscription.openInBrowser]: shortcuts.subscriptions.openInBrowser.key,
|
||||
[COMMAND_ID.subscription.openSiteInBrowser]: shortcuts.subscriptions.openSiteInBrowser.key,
|
||||
[COMMAND_ID.subscription.previousSubscription]: shortcuts.subscriptions.previousSubscription.key,
|
||||
[COMMAND_ID.subscription.switchTabToNext]: shortcuts.subscriptions.switchNextView.key,
|
||||
[COMMAND_ID.subscription.switchTabToPrevious]: shortcuts.subscriptions.switchPreviousView.key,
|
||||
|
||||
[COMMAND_ID.global.showShortcuts]: shortcuts.layout.showShortcuts.key,
|
||||
|
||||
[COMMAND_ID.entryRender.nextEntry]: shortcuts.entry.nextEntry.key,
|
||||
[COMMAND_ID.entryRender.previousEntry]: shortcuts.entry.previousEntry.key,
|
||||
|
||||
[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,
|
||||
|
||||
// Timeline commands
|
||||
[COMMAND_ID.timeline.refetch]: shortcuts.entries.refetch.key,
|
||||
[COMMAND_ID.timeline.switchToNext]: shortcuts.entries.next.key,
|
||||
[COMMAND_ID.timeline.switchToPrevious]: shortcuts.entries.previous.key,
|
||||
[COMMAND_ID.timeline.unreadOnly]: shortcuts.entries.toggleUnreadOnly.key,
|
||||
} as const
|
||||
|
||||
export type BindingCommandId = keyof typeof defaultCommandShortcuts
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { jotaiStore } from "@follow/utils/jotai"
|
||||
import { useAtomValue } from "jotai"
|
||||
import { selectAtom } from "jotai/utils"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import { CommandRegistry } from "../registry/registry"
|
||||
import type { FollowCommandId, FollowCommandMap } from "../types"
|
||||
|
|
@ -25,14 +25,16 @@ export function useCommand<T extends FollowCommandId>(id: T): FollowCommandMap[T
|
|||
}
|
||||
|
||||
const noop = () => {}
|
||||
export function useRunCommandFn() {
|
||||
return useCallback(
|
||||
<T extends FollowCommandId>(id: T, args: Parameters<FollowCommandMap[T]["run"]>) => {
|
||||
const cmd = getCommand(id)
|
||||
if (!cmd) return noop
|
||||
// @ts-expect-error - The type should be discriminated
|
||||
return () => cmd.run(...args)
|
||||
},
|
||||
[],
|
||||
)
|
||||
const runCommand = <T extends FollowCommandId>(
|
||||
id: T,
|
||||
args: Parameters<FollowCommandMap[T]["run"]>,
|
||||
) => {
|
||||
const cmd = getCommand(id)
|
||||
|
||||
if (!cmd) return noop
|
||||
// @ts-expect-error - The type should be discriminated
|
||||
return () => cmd.run(...args)
|
||||
}
|
||||
export function useRunCommandFn() {
|
||||
return runCommand
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ import { Trans, useTranslation } from "react-i18next"
|
|||
import { toast } from "sonner"
|
||||
|
||||
import { HotkeyScope } from "~/constants"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { useI18n } from "~/hooks/common"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { useCommandBinding } from "~/modules/command/hooks/use-command-binding"
|
||||
import { useCommandBinding, useCommandShortcuts } from "~/modules/command/hooks/use-command-binding"
|
||||
import { useHotkeyScope } from "~/providers/hotkey-provider"
|
||||
|
||||
import type { MarkAllFilter } from "../hooks/useMarkAll"
|
||||
|
|
@ -71,6 +70,8 @@ export const MarkAllReadButton = ({
|
|||
})
|
||||
})
|
||||
}, [t])
|
||||
|
||||
const markAllAsReadShortcut = useCommandShortcuts()[COMMAND_ID.subscription.markAllAsRead]
|
||||
return (
|
||||
<ActionButton
|
||||
tooltip={
|
||||
|
|
@ -83,9 +84,7 @@ export const MarkAllReadButton = ({
|
|||
/>
|
||||
{shortcut && (
|
||||
<div className="ml-1">
|
||||
<KbdCombined className="text-text-secondary">
|
||||
{shortcuts.subscriptions.markAllAsRead.key}
|
||||
</KbdCombined>
|
||||
<KbdCombined className="text-text-secondary">{markAllAsReadShortcut}</KbdCombined>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ import { useTranslation } from "react-i18next"
|
|||
import { useNavigate } from "react-router"
|
||||
|
||||
import { previewBackPath } from "~/atoms/preview"
|
||||
import { setGeneralSetting, useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useTimelineColumnShow } from "~/atoms/sidebar"
|
||||
import { useWhoami } from "~/atoms/user"
|
||||
import { FEED_COLLECTION_LIST, ROUTE_ENTRY_PENDING } from "~/constants"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { useFollow } from "~/hooks/biz/useFollow"
|
||||
import { getRouteParams, useRouteParams } from "~/hooks/biz/useRouteParams"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { useRunCommandFn } from "~/modules/command/hooks/use-command"
|
||||
import { useCommandShortcuts } from "~/modules/command/hooks/use-command-binding"
|
||||
import { EntryHeader } from "~/modules/entry-content/header"
|
||||
import { useRefreshFeedMutation } from "~/queries/feed"
|
||||
import { getFeedById, useFeedById, useFeedHeaderTitle } from "~/store/feed"
|
||||
|
|
@ -64,7 +66,8 @@ export const EntryListHeader: FC<{
|
|||
|
||||
const titleStyleBasedView = ["pl-6", "pl-7", "pl-7", "pl-7", "px-5", "pl-6"]
|
||||
const feedColumnShow = useTimelineColumnShow()
|
||||
|
||||
const commandShortcuts = useCommandShortcuts()
|
||||
const runCmdFn = useRunCommandFn()
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -134,8 +137,8 @@ export const EntryListHeader: FC<{
|
|||
? t("entry_list_header.show_unread_only")
|
||||
: t("entry_list_header.show_all")
|
||||
}
|
||||
shortcut={shortcuts.entries.toggleUnreadOnly.key}
|
||||
onClick={() => setGeneralSetting("unreadOnly", !unreadOnly)}
|
||||
shortcut={commandShortcuts[COMMAND_ID.timeline.unreadOnly]}
|
||||
onClick={() => runCmdFn(COMMAND_ID.timeline.unreadOnly, [!unreadOnly])()}
|
||||
>
|
||||
{unreadOnly ? (
|
||||
<i className="i-mgc-round-cute-fi" />
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@ import { cn, isBizId } from "@follow/utils/utils"
|
|||
import type { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { setGeneralSetting, useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useWhoami } from "~/atoms/user"
|
||||
import { HeaderTopReturnBackButton } from "~/components/mobile/button"
|
||||
import { FEED_COLLECTION_LIST } from "~/constants"
|
||||
import { LOGO_MOBILE_ID } from "~/constants/dom"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { useRouteParams } from "~/hooks/biz/useRouteParams"
|
||||
import { FeedColumnMobile } from "~/modules/app-layout/feed-column/mobile"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { useRunCommandFn } from "~/modules/command/hooks/use-command"
|
||||
import { useRefreshFeedMutation } from "~/queries/feed"
|
||||
import { useFeedById, useFeedHeaderTitle } from "~/store/feed"
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ export const EntryListHeader: FC<EntryListHeaderProps> = ({ refetch, isRefreshin
|
|||
const isList = !!listId
|
||||
|
||||
const showQuickTimeline = useGeneralSettingKey("showQuickTimeline")
|
||||
const runCmdFn = useRunCommandFn()
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -130,8 +132,7 @@ export const EntryListHeader: FC<EntryListHeaderProps> = ({ refetch, isRefreshin
|
|||
? t("entry_list_header.show_unread_only")
|
||||
: t("entry_list_header.show_all")
|
||||
}
|
||||
shortcut={shortcuts.entries.toggleUnreadOnly.key}
|
||||
onClick={() => setGeneralSetting("unreadOnly", !unreadOnly)}
|
||||
onClick={() => runCmdFn(COMMAND_ID.timeline.unreadOnly, [!unreadOnly])()}
|
||||
>
|
||||
{unreadOnly ? (
|
||||
<i className="i-mgc-round-cute-fi" />
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ import {
|
|||
useSetZenMode,
|
||||
useUISettingKey,
|
||||
} from "~/atoms/settings/ui"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { useAIDailyReportModal } from "~/modules/ai/ai-daily/useAIDailyReportModal"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { useCommandShortcuts } from "~/modules/command/hooks/use-command-binding"
|
||||
|
||||
import { setMasonryColumnValue, useMasonryColumnValue } from "../atoms"
|
||||
|
||||
|
|
@ -146,9 +147,10 @@ export const WideModeButton = () => {
|
|||
const { t } = useTranslation()
|
||||
|
||||
const setIsZenMode = useSetZenMode()
|
||||
const shortcuts = useCommandShortcuts()
|
||||
return (
|
||||
<ActionButton
|
||||
shortcut={shortcuts.layout.toggleWideMode.key}
|
||||
shortcut={shortcuts[COMMAND_ID.layout.toggleWideMode]}
|
||||
onClick={() => {
|
||||
if (isZenMode) {
|
||||
setIsZenMode(false)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import { signOut, useSession } from "~/queries/auth"
|
|||
import { useWallet } from "~/queries/wallet"
|
||||
|
||||
import { useActivationModal } from "../activation"
|
||||
import { COMMAND_ID } from "../command/commands/id"
|
||||
import { useCommandShortcuts } from "../command/hooks/use-command-binding"
|
||||
import { ActivityPoints } from "../wallet/activity-points"
|
||||
import { Level } from "../wallet/level"
|
||||
import type { LoginProps } from "./LoginButton"
|
||||
|
|
@ -65,6 +67,8 @@ export const ProfileButton: FC<ProfileButtonProps> = memo((props) => {
|
|||
const setZenMode = useSetZenMode()
|
||||
const isInMASReview = useIsInMASReview()
|
||||
|
||||
const shortcuts = useCommandShortcuts()
|
||||
|
||||
if (status !== "authenticated") {
|
||||
return <LoginButton {...props} />
|
||||
}
|
||||
|
|
@ -156,6 +160,7 @@ export const ProfileButton: FC<ProfileButtonProps> = memo((props) => {
|
|||
setZenMode(true)
|
||||
}}
|
||||
icon={<MdiMeditation className="size-4" />}
|
||||
shortcut={shortcuts[COMMAND_ID.layout.toggleZenMode]}
|
||||
>
|
||||
{t("user_button.zen_mode")}
|
||||
</DropdownMenuItem>
|
||||
|
|
@ -188,6 +193,7 @@ export const ProfileButton: FC<ProfileButtonProps> = memo((props) => {
|
|||
settingModalPresent()
|
||||
}}
|
||||
icon={<i className="i-mgc-settings-7-cute-re" />}
|
||||
shortcut={"$mod+,"}
|
||||
>
|
||||
{t("user_button.preferences")}
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { highlightElement } from "@follow/components/common/Focusable/utils.js"
|
||||
import { nextFrame } from "@follow/utils/dom"
|
||||
import { nextFrame, preventDefault, stopPropagation } from "@follow/utils/dom"
|
||||
import { EventBus } from "@follow/utils/event-bus"
|
||||
import { useEffect } from "react"
|
||||
import { tinykeys } from "tinykeys"
|
||||
|
|
@ -7,7 +7,8 @@ import { useEventListener } from "usehooks-ts"
|
|||
|
||||
import { HotkeyScope } from "~/constants/hotkeys"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { useCommandBinding } from "~/modules/command/hooks/use-command-binding"
|
||||
import { useRunCommandFn } from "~/modules/command/hooks/use-command"
|
||||
import { useCommandBinding, useCommandShortcuts } from "~/modules/command/hooks/use-command-binding"
|
||||
|
||||
import { useHotkeyScope } from "./hotkey-provider"
|
||||
|
||||
|
|
@ -54,14 +55,26 @@ export const GlobalHotkeysProvider = () => {
|
|||
}
|
||||
})
|
||||
|
||||
// Show current focused element
|
||||
const commandShortcuts = useCommandShortcuts()
|
||||
|
||||
const runCommandFn = useRunCommandFn()
|
||||
useEffect(() => {
|
||||
const preHandler = (e: Event) => {
|
||||
stopPropagation(e)
|
||||
preventDefault(e)
|
||||
}
|
||||
return tinykeys(window, {
|
||||
"$mod+Period": () => {
|
||||
// Show current focused element
|
||||
"$mod+Period": (e) => {
|
||||
preHandler(e)
|
||||
highlightElement(document.activeElement as HTMLElement)
|
||||
},
|
||||
[commandShortcuts[COMMAND_ID.layout.toggleZenMode]]: (e) => {
|
||||
preHandler(e)
|
||||
runCommandFn(COMMAND_ID.layout.toggleZenMode, [])()
|
||||
},
|
||||
})
|
||||
}, [])
|
||||
}, [commandShortcuts, runCommandFn])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
"keys.entry.toggleRead": "Toggle Read",
|
||||
"keys.entry.toggleStarred": "Toggle Starred",
|
||||
"keys.entry.tts": "Play TTS",
|
||||
"keys.layout.showShortcuts": "Show/Hide Shortcuts",
|
||||
"keys.layout.toggleSidebar": "Show/Hide Feed Sidebar",
|
||||
"keys.layout.toggleWideMode": "Toggle Wide Mode",
|
||||
"keys.layout.zenMode": "Zen Mode",
|
||||
"keys.misc.quickSearch": "Quick Search",
|
||||
"keys.misc.showShortcuts": "Show/Hide Shortcuts",
|
||||
"keys.subscriptions.add": "Add Subscription",
|
||||
"keys.subscriptions.nextSubscription": "Next Subscription",
|
||||
"keys.subscriptions.openInBrowser": "Open in Browser",
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
"keys.entry.toggleRead": "既読/未読の切り替え",
|
||||
"keys.entry.toggleStarred": "スターの切り替え",
|
||||
"keys.entry.tts": "TTS を再生",
|
||||
"keys.layout.showShortcuts": "ショートカットを表示/非表示",
|
||||
"keys.layout.toggleSidebar": "フィードサイドバーの表示/非表示",
|
||||
"keys.layout.toggleWideMode": "ワイドモードに切り替え",
|
||||
"keys.layout.zenMode": "Zen モード",
|
||||
"keys.misc.quickSearch": "クイック検索",
|
||||
"keys.misc.showShortcuts": "ショートカットを表示/非表示",
|
||||
"keys.subscriptions.add": "購読を追加",
|
||||
"keys.subscriptions.switchBetweenViews": "ビューを切り替え",
|
||||
"keys.subscriptions.switchToView": "ビューの切り替え",
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
"keys.entry.toggleRead": "标记为已读/未读",
|
||||
"keys.entry.toggleStarred": "加入收藏/取消收藏",
|
||||
"keys.entry.tts": "文本转语音",
|
||||
"keys.layout.showShortcuts": "显示/隐藏快捷键",
|
||||
"keys.layout.toggleSidebar": "显示/隐藏侧边栏",
|
||||
"keys.layout.toggleWideMode": "切换宽屏模式",
|
||||
"keys.layout.zenMode": "禅定模式",
|
||||
"keys.misc.quickSearch": "快速搜索",
|
||||
"keys.misc.showShortcuts": "显示/隐藏快捷键",
|
||||
"keys.subscriptions.add": "添加订阅源",
|
||||
"keys.subscriptions.nextSubscription": "下一个订阅",
|
||||
"keys.subscriptions.previousSubscription": "上一个订阅",
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
"keys.entry.toggleRead": "切換標記為已讀/未讀",
|
||||
"keys.entry.toggleStarred": "切換收藏/取消收藏",
|
||||
"keys.entry.tts": "播放文字轉語音",
|
||||
"keys.layout.showShortcuts": "顯示/隱藏快捷鍵",
|
||||
"keys.layout.toggleSidebar": "顯示/隱藏摘要側邊欄",
|
||||
"keys.layout.toggleWideMode": "切換寬螢幕模式",
|
||||
"keys.layout.zenMode": "禪定模式",
|
||||
"keys.misc.quickSearch": "快速搜尋",
|
||||
"keys.misc.showShortcuts": "顯示/隱藏快捷鍵",
|
||||
"keys.subscriptions.add": "新增 RSS 摘要",
|
||||
"keys.subscriptions.switchBetweenViews": "在類別之間切換",
|
||||
"keys.subscriptions.switchToView": "切換到指定類別",
|
||||
|
|
|
|||
|
|
@ -156,10 +156,10 @@ export function highlightElement(element: HTMLElement | null, colorString = "255
|
|||
ctx.lineWidth = shadowWidth
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(
|
||||
padding - shadowWidth / 2,
|
||||
padding - shadowWidth / 2,
|
||||
rect.width + shadowWidth,
|
||||
rect.height + shadowWidth,
|
||||
padding + shadowWidth / 2,
|
||||
padding + shadowWidth / 2,
|
||||
rect.width - shadowWidth,
|
||||
rect.height - shadowWidth,
|
||||
borderRadius,
|
||||
)
|
||||
ctx.stroke()
|
||||
|
|
@ -171,10 +171,10 @@ export function highlightElement(element: HTMLElement | null, colorString = "255
|
|||
ctx.lineWidth = outlineWidth
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(
|
||||
padding - outlineWidth / 2,
|
||||
padding - outlineWidth / 2,
|
||||
rect.width + outlineWidth,
|
||||
rect.height + outlineWidth,
|
||||
padding + outlineWidth / 2,
|
||||
padding + outlineWidth / 2,
|
||||
rect.width - outlineWidth,
|
||||
rect.height - outlineWidth,
|
||||
borderRadius,
|
||||
)
|
||||
ctx.stroke()
|
||||
|
|
|
|||
|
|
@ -299,34 +299,35 @@ function simulateKeyPress(key: string) {
|
|||
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
export const Kbd: FC<{ children: string; className?: string }> = memo(({ children, className }) => {
|
||||
let specialKeys = (SpecialKeys as any)[os] as Record<string, string>
|
||||
specialKeys = { ...SharedKeys, ...specialKeys }
|
||||
export const Kbd: FC<{ children: string; className?: string; wrapButton?: boolean }> = memo(
|
||||
({ children, className, wrapButton = true }) => {
|
||||
let specialKeys = (SpecialKeys as any)[os] as Record<string, string>
|
||||
specialKeys = { ...SharedKeys, ...specialKeys }
|
||||
|
||||
const [isKeyPressed, setIsKeyPressed] = React.useState(false)
|
||||
React.useEffect(() => {
|
||||
const handler = () => {
|
||||
setIsKeyPressed(isHotkeyPressed(children.toLowerCase()))
|
||||
}
|
||||
document.addEventListener("keydown", handler)
|
||||
document.addEventListener("keyup", handler)
|
||||
const [isKeyPressed, setIsKeyPressed] = React.useState(false)
|
||||
React.useEffect(() => {
|
||||
const handler = () => {
|
||||
setIsKeyPressed(isHotkeyPressed(children.toLowerCase()))
|
||||
}
|
||||
document.addEventListener("keydown", handler)
|
||||
document.addEventListener("keyup", handler)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handler)
|
||||
document.removeEventListener("keyup", handler)
|
||||
}
|
||||
}, [children])
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handler)
|
||||
document.removeEventListener("keyup", handler)
|
||||
}
|
||||
}, [children])
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
setIsKeyPressed(true)
|
||||
setTimeout(() => {
|
||||
setIsKeyPressed(false)
|
||||
}, 100)
|
||||
const handleClick = React.useCallback(() => {
|
||||
setIsKeyPressed(true)
|
||||
setTimeout(() => {
|
||||
setIsKeyPressed(false)
|
||||
}, 100)
|
||||
|
||||
simulateKeyPress(children.trim())
|
||||
}, [children])
|
||||
return (
|
||||
<button type="button" onClick={handleClick}>
|
||||
simulateKeyPress(children.trim())
|
||||
}, [children])
|
||||
|
||||
const Kbd = (
|
||||
<kbd
|
||||
className={cn(
|
||||
"kbd text-text box-border h-5 space-x-1 font-sans text-[0.7rem]",
|
||||
|
|
@ -380,9 +381,16 @@ export const Kbd: FC<{ children: string; className?: string }> = memo(({ childre
|
|||
}
|
||||
})}
|
||||
</kbd>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
)
|
||||
return wrapButton ? (
|
||||
<button type="button" onClick={handleClick}>
|
||||
{Kbd}
|
||||
</button>
|
||||
) : (
|
||||
Kbd
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
function MaterialSymbolsKeyboardCommandKey(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue