diff --git a/apps/desktop/layer/renderer/src/constants/hotkeys.ts b/apps/desktop/layer/renderer/src/constants/hotkeys.ts
index bf62b4f95..4365ba12f 100644
--- a/apps/desktop/layer/renderer/src/constants/hotkeys.ts
+++ b/apps/desktop/layer/renderer/src/constants/hotkeys.ts
@@ -8,4 +8,5 @@ export enum HotkeyScope {
Timeline = "timeline",
EntryRender = "entry-render",
SubscriptionList = "subscription-list",
+ SubLayer = "sub-layer",
}
diff --git a/apps/desktop/layer/renderer/src/modules/app-layout/feed-column/desktop.tsx b/apps/desktop/layer/renderer/src/modules/app-layout/feed-column/desktop.tsx
index 5bc5ec81a..61920945a 100644
--- a/apps/desktop/layer/renderer/src/modules/app-layout/feed-column/desktop.tsx
+++ b/apps/desktop/layer/renderer/src/modules/app-layout/feed-column/desktop.tsx
@@ -39,7 +39,7 @@ import { EnvironmentIndicator } from "~/modules/app/EnvironmentIndicator"
import { NetworkStatusIndicator } from "~/modules/app/NetworkStatusIndicator"
import { LoginModalContent } from "~/modules/auth/LoginModalContent"
import { COMMAND_ID } from "~/modules/command/commands/id"
-import { useCommandBinding } from "~/modules/command/hooks/use-register-hotkey"
+import { useCommandBinding } from "~/modules/command/hooks/use-command-binding"
import { DebugRegistry } from "~/modules/debug/registry"
import { CmdF } from "~/modules/panel/cmdf"
import { SearchCmdK } from "~/modules/panel/cmdk"
diff --git a/apps/desktop/layer/renderer/src/modules/app-layout/subview/index.electron.tsx b/apps/desktop/layer/renderer/src/modules/app-layout/subview/index.electron.tsx
index f100dcdec..ba98e326d 100644
--- a/apps/desktop/layer/renderer/src/modules/app-layout/subview/index.electron.tsx
+++ b/apps/desktop/layer/renderer/src/modules/app-layout/subview/index.electron.tsx
@@ -6,10 +6,13 @@ import { ELECTRON_BUILD } from "@follow/shared/constants"
import { springScrollTo } from "@follow/utils/scroller"
import { cn, getOS } from "@follow/utils/utils"
import { useEffect, useRef, useState } from "react"
+import { useHotkeys } from "react-hotkeys-hook"
import { useTranslation } from "react-i18next"
import { NavigationType, Outlet, useLocation, useNavigate, useNavigationType } from "react-router"
import { FABContainer, FABPortable } from "~/components/ui/fab"
+import { HotkeyScope } from "~/constants"
+import { useConditionalHotkeyScope } from "~/hooks/common"
import { useSubViewTitleValue } from "./hooks"
@@ -53,6 +56,16 @@ export function SubviewLayout() {
// electron window has pt-[calc(var(--fo-window-padding-top)_-10px)]
const isElectronWindows = ELECTRON_BUILD && getOS() === "Windows"
+ useConditionalHotkeyScope(HotkeyScope.SubLayer, true)
+
+ const backHandler = () => {
+ if (prevLocation.pathname === location.pathname) {
+ navigate({ pathname: "" })
+ } else {
+ navigate(prevLocation)
+ }
+ }
+ useHotkeys("esc", backHandler)
return (
{
- if (prevLocation.pathname === location.pathname) {
- navigate({ pathname: "" })
- } else {
- navigate(prevLocation)
- }
- }}
+ onClick={backHandler}
className="no-drag-region hover:text-accent inline-flex items-center gap-1 duration-200"
>
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 629e08af5..db7ee28eb 100644
--- a/apps/desktop/layer/renderer/src/modules/command/commands/id.ts
+++ b/apps/desktop/layer/renderer/src/modules/command/commands/id.ts
@@ -48,12 +48,12 @@ export const COMMAND_ID = {
toggleTimelineColumn: "layout:toggle-timeline-column",
focusToTimeline: "layout:focus-to-timeline",
focusToSubscription: "layout:focus-to-subscription",
+ focusToEntryRender: "layout:focus-to-entry-render",
},
timeline: {
switchToNext: "timeline:switch-to-next",
switchToPrevious: "timeline:switch-to-previous",
refetch: "timeline:refetch",
- enter: "timeline:enter",
},
entryRender: {
scrollDown: "entry-render:scroll-down",
diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/layout.tsx b/apps/desktop/layer/renderer/src/modules/command/commands/layout.tsx
index 9c7aadd09..219631e65 100644
--- a/apps/desktop/layer/renderer/src/modules/command/commands/layout.tsx
+++ b/apps/desktop/layer/renderer/src/modules/command/commands/layout.tsx
@@ -10,6 +10,7 @@ declare module "@follow/utils/event-bus" {
interface EventBusMap {
"layout:focus-to-timeline": never
"layout:focus-to-subscription": never
+ "layout:focus-to-entry-render": never
}
}
@@ -36,6 +37,13 @@ export const useRegisterLayoutCommands = () => {
EventBus.dispatch(COMMAND_ID.layout.focusToSubscription)
},
},
+ {
+ id: COMMAND_ID.layout.focusToEntryRender,
+ label: "Enter Selected Entry",
+ run: () => {
+ EventBus.dispatch(COMMAND_ID.layout.focusToEntryRender)
+ },
+ },
])
}
@@ -53,7 +61,12 @@ export type FocusToTimelineCommand = Command<{
id: typeof COMMAND_ID.layout.focusToTimeline
fn: () => void
}>
+export type FocusToEntryRenderCommand = Command<{
+ id: typeof COMMAND_ID.layout.focusToEntryRender
+ fn: () => void
+}>
export type LayoutCommand =
| ToggleTimelineColumnCommand
| FocusToTimelineCommand
| FocusToSubscriptionCommand
+ | FocusToEntryRenderCommand
diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/timeline.tsx b/apps/desktop/layer/renderer/src/modules/command/commands/timeline.tsx
index 3f6d9e809..2f3ee4fd1 100644
--- a/apps/desktop/layer/renderer/src/modules/command/commands/timeline.tsx
+++ b/apps/desktop/layer/renderer/src/modules/command/commands/timeline.tsx
@@ -36,13 +36,6 @@ export const useRegisterTimelineCommand = () => {
EventBus.dispatch("timeline:refetch")
},
},
- {
- id: COMMAND_ID.timeline.enter,
- label: "Enter Selected Entry",
- run: () => {
- EventBus.dispatch("timeline:enter")
- },
- },
])
}
@@ -61,13 +54,7 @@ export type RefetchTimelineCommand = Command<{
fn: () => void
}>
-export type EnterTimelineCommand = Command<{
- id: typeof COMMAND_ID.timeline.enter
- fn: () => void
-}>
-
export type TimelineCommand =
| SwitchToNextTimelineCommand
| SwitchToPreviousTimelineCommand
| RefetchTimelineCommand
- | EnterTimelineCommand
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
new file mode 100644
index 000000000..d1631750a
--- /dev/null
+++ b/apps/desktop/layer/renderer/src/modules/command/hooks/use-command-binding.ts
@@ -0,0 +1,19 @@
+import type { BindingCommandId } from "./use-command-shortcut"
+import { useCommandShortcut } from "./use-command-shortcut"
+import type { RegisterHotkeyOptions } from "./use-register-hotkey"
+import { useCommandHotkey } from "./use-register-hotkey"
+
+export const useCommandBinding = ({
+ commandId,
+ when = true,
+ args,
+}: Omit, "shortcut">) => {
+ const commandShortcut = useCommandShortcut(commandId)
+
+ return useCommandHotkey({
+ shortcut: commandShortcut,
+ commandId,
+ when,
+ args,
+ })
+}
diff --git a/apps/desktop/layer/renderer/src/modules/command/hooks/use-register-hotkey.ts b/apps/desktop/layer/renderer/src/modules/command/hooks/use-register-hotkey.ts
index 89a19a151..261b2627e 100644
--- a/apps/desktop/layer/renderer/src/modules/command/hooks/use-register-hotkey.ts
+++ b/apps/desktop/layer/renderer/src/modules/command/hooks/use-register-hotkey.ts
@@ -4,13 +4,11 @@ import { tinykeys } from "tinykeys"
import type { FollowCommand, FollowCommandId } from "../types"
import { getCommand } from "./use-command"
-import type { BindingCommandId } from "./use-command-shortcut"
-import { useCommandShortcut } from "./use-command-shortcut"
export interface HotkeyOptions {
forceInputElement?: true
}
-interface RegisterHotkeyOptions {
+export interface RegisterHotkeyOptions {
shortcut: string
commandId: T
args?: Parameters["run"]>
@@ -80,18 +78,3 @@ export const useCommandHotkey = ({
return tinykeys(document.documentElement, keyMap)
}, [shortcut, commandId, when, argsRef, options?.forceInputElement])
}
-
-export const useCommandBinding = ({
- commandId,
- when = true,
- args,
-}: Omit, "shortcut">) => {
- const commandShortcut = useCommandShortcut(commandId)
-
- return useCommandHotkey({
- shortcut: commandShortcut,
- commandId,
- when,
- args,
- })
-}
diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/EntryColumnShortcutHandler.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/EntryColumnShortcutHandler.tsx
index 3f1cc5528..cc64dc81d 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-column/EntryColumnShortcutHandler.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-column/EntryColumnShortcutHandler.tsx
@@ -13,7 +13,8 @@ import { useConditionalHotkeyScope } from "~/hooks/common"
import { useHotkeyScope } from "~/providers/hotkey-provider"
import { COMMAND_ID } from "../command/commands/id"
-import { useCommandBinding, useCommandHotkey } from "../command/hooks/use-register-hotkey"
+import { useCommandBinding } from "../command/hooks/use-command-binding"
+import { useCommandHotkey } from "../command/hooks/use-register-hotkey"
export const EntryColumnShortcutHandler: FC<{
refetch: () => void
@@ -43,7 +44,7 @@ export const EntryColumnShortcutHandler: FC<{
})
useCommandHotkey({
- commandId: COMMAND_ID.timeline.enter,
+ commandId: COMMAND_ID.layout.focusToEntryRender,
shortcut: "Enter",
when,
})
diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/actions/header-actions.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/actions/header-actions.tsx
index a76305d51..0255e0ef1 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-content/actions/header-actions.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-content/actions/header-actions.tsx
@@ -5,7 +5,7 @@ import { CommandActionButton } from "~/components/ui/button/CommandActionButton"
import { useHasModal } from "~/components/ui/modal/stacked/hooks"
import { useSortedEntryActions } from "~/hooks/biz/useEntryActions"
import { COMMAND_ID } from "~/modules/command/commands/id"
-import { useCommandBinding } from "~/modules/command/hooks/use-register-hotkey"
+import { useCommandBinding } from "~/modules/command/hooks/use-command-binding"
import { useEntry } from "~/store/entry/hooks"
export const EntryHeaderActions = ({
diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx
index 6155a9183..fb1188cd1 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx
@@ -34,7 +34,8 @@ import { useFeedById } from "~/store/feed"
import { useInboxById } from "~/store/inbox"
import { COMMAND_ID } from "../command/commands/id"
-import { useCommandBinding, useCommandHotkey } from "../command/hooks/use-register-hotkey"
+import { useCommandBinding } from "../command/hooks/use-command-binding"
+import { useCommandHotkey } from "../command/hooks/use-register-hotkey"
import { EntryContentHTMLRenderer } from "../renderer/html"
import { AISummary } from "./AISummary"
import { EntryTimelineSidebar } from "./components/EntryTimelineSidebar"
@@ -235,6 +236,7 @@ const EntryScrollArea: Component<{
}
return (
{
+ EventBus.subscribe(COMMAND_ID.layout.focusToEntryRender, () => {
const $scroller = scrollerRef.current
if ($scroller) {
springScrollTo(0, $scroller)
diff --git a/apps/desktop/layer/renderer/src/modules/timeline-column/FeedList.electron.tsx b/apps/desktop/layer/renderer/src/modules/timeline-column/FeedList.electron.tsx
index fafcd4545..f725975c4 100644
--- a/apps/desktop/layer/renderer/src/modules/timeline-column/FeedList.electron.tsx
+++ b/apps/desktop/layer/renderer/src/modules/timeline-column/FeedList.electron.tsx
@@ -26,7 +26,8 @@ import {
} from "~/store/subscription"
import { COMMAND_ID } from "../command/commands/id"
-import { useCommandBinding, useCommandHotkey } from "../command/hooks/use-register-hotkey"
+import { useCommandBinding } from "../command/hooks/use-command-binding"
+import { useCommandHotkey } from "../command/hooks/use-register-hotkey"
import { useIsPreviewFeed } from "../entry-column/hooks/useIsPreviewFeed"
import {
resetSelectedFeedIds,
diff --git a/apps/desktop/layer/renderer/src/modules/timeline-column/index.tsx b/apps/desktop/layer/renderer/src/modules/timeline-column/index.tsx
index e56f7d533..79cd26853 100644
--- a/apps/desktop/layer/renderer/src/modules/timeline-column/index.tsx
+++ b/apps/desktop/layer/renderer/src/modules/timeline-column/index.tsx
@@ -27,7 +27,7 @@ import { useHotkeyScope } from "~/providers/hotkey-provider"
import { WindowUnderBlur } from "../../components/ui/background"
import { COMMAND_ID } from "../command/commands/id"
-import { useCommandBinding } from "../command/hooks/use-register-hotkey"
+import { useCommandBinding } from "../command/hooks/use-command-binding"
import { getSelectedFeedIds, resetSelectedFeedIds, setSelectedFeedIds } from "./atom"
import { useShouldFreeUpSpace } from "./hook"
import { TimelineColumnHeader } from "./TimelineColumnHeader"
diff --git a/apps/desktop/layer/renderer/src/providers/global-hotkeys-provider.tsx b/apps/desktop/layer/renderer/src/providers/global-hotkeys-provider.tsx
index d8e7971d3..5afe02854 100644
--- a/apps/desktop/layer/renderer/src/providers/global-hotkeys-provider.tsx
+++ b/apps/desktop/layer/renderer/src/providers/global-hotkeys-provider.tsx
@@ -1,10 +1,13 @@
import { highlightElement } from "@follow/components/common/Focusable/utils.js"
import { nextFrame } from "@follow/utils/dom"
+import { EventBus } from "@follow/utils/event-bus"
+import { useEffect } from "react"
+import { tinykeys } from "tinykeys"
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-register-hotkey"
+import { useCommandBinding } from "~/modules/command/hooks/use-command-binding"
import { useHotkeyScope } from "./hotkey-provider"
@@ -31,5 +34,36 @@ export const GlobalHotkeysProvider = () => {
}
})
+ // Re force to sidebar focusable
+ useEventListener("focusin", (e) => {
+ if (
+ activeScopes.length === 1 &&
+ activeScopes[0] === HotkeyScope.Home &&
+ e.target === document.body
+ ) {
+ EventBus.dispatch(COMMAND_ID.layout.focusToTimeline)
+ }
+ })
+ // Re force to sidebar focusable
+ useEventListener("focusout", () => {
+ const { activeElement } = document
+ if (
+ activeElement === document.body &&
+ activeScopes.length === 1 &&
+ activeScopes[0] === HotkeyScope.Home
+ ) {
+ EventBus.dispatch(COMMAND_ID.layout.focusToTimeline)
+ }
+ })
+
+ // Show current focused element
+ useEffect(() => {
+ return tinykeys(window, {
+ "$mod+Period": () => {
+ highlightElement(document.activeElement as HTMLElement)
+ },
+ })
+ }, [])
+
return null
}
diff --git a/packages/internal/components/src/ui/scroll-area/ScrollArea.tsx b/packages/internal/components/src/ui/scroll-area/ScrollArea.tsx
index 2c1964647..682d4ef36 100644
--- a/packages/internal/components/src/ui/scroll-area/ScrollArea.tsx
+++ b/packages/internal/components/src/ui/scroll-area/ScrollArea.tsx
@@ -75,9 +75,11 @@ const Viewport = ({
ref: forwardedRef,
className,
mask = false,
+ focusable = true,
...rest
}: React.ComponentPropsWithoutRef & {
mask?: boolean
+ focusable?: boolean
} & { ref?: React.Ref | null> }) => {
const ref = React.useRef(null)
const [shouldAddMask, setShouldAddMask] = React.useState(false)
@@ -105,7 +107,7 @@ const Viewport = ({
)
@@ -143,6 +145,8 @@ export const ScrollArea = ({
onScroll,
orientation = "vertical",
asChild = false,
+
+ focusable = true,
}: React.PropsWithChildren & {
rootClassName?: string
viewportClassName?: string
@@ -152,6 +156,7 @@ export const ScrollArea = ({
onScroll?: (e: React.UIEvent) => void
orientation?: "vertical" | "horizontal"
asChild?: boolean
+ focusable?: boolean
} & { ref?: React.Ref }) => {
const [viewportRef, setViewportRef] = React.useState(null)
React.useImperativeHandle(ref, () => viewportRef as HTMLDivElement)
@@ -166,6 +171,7 @@ export const ScrollArea = ({
mask={mask}
asChild={asChild}
onScroll={onScroll}
+ focusable={focusable}
>
{children}