fix: context menu prevent default and shortcut handler

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-07-30 12:15:01 +08:00
parent 670a45e482
commit b6042afc5e
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
4 changed files with 120 additions and 95 deletions

View File

@ -104,6 +104,7 @@ const MediaImpl: FC<MediaProps> = ({
{
onContextMenu: (e) => {
e.stopPropagation()
e.preventDefault()
props.onContextMenu?.(e)
showNativeMenu(
[

View File

@ -0,0 +1,98 @@
import { useMainContainerElement } from "@renderer/atoms/dom"
import { shortcuts } from "@renderer/constants/shortcuts"
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
import { useRouteEntryId } from "@renderer/hooks/biz/useRouteParams"
import { useRefValue } from "@renderer/hooks/common"
import type { FC } from "react"
import { memo, useLayoutEffect, useState } from "react"
import { useHotkeys } from "react-hotkeys-hook"
import type { VirtuosoHandle } from "react-virtuoso"
export const EntryColumnShortcutHandler: FC<{
refetch: () => void
data: readonly string[]
virtuosoRef: React.RefObject<VirtuosoHandle>
}> = memo(({ data, refetch, virtuosoRef }) => {
const dataRef = useRefValue(data!)
useHotkeys(
shortcuts.entries.refetch.key,
() => {
refetch()
},
{ scopes: ["home"] },
)
const currentEntryIdRef = useRefValue(useRouteEntryId())
const navigate = useNavigateEntry()
const $mainContainer = useMainContainerElement()
const [enabledArrowKey, setEnabledArrowKey] = useState(false)
// Enable arrow key navigation shortcuts only when focus is on entryContent or entryList,
// entryList shortcuts should not be triggered in the feed col
useLayoutEffect(() => {
if (!$mainContainer) return
const handler = () => {
const target = document.activeElement
const isFocusIn =
$mainContainer.contains(target) || $mainContainer === target
setEnabledArrowKey(isFocusIn)
}
handler()
// NOTE: focusin event will bubble to the document
document.addEventListener("focusin", handler)
return () => {
document.removeEventListener("focusin", handler)
}
}, [$mainContainer])
useHotkeys(
shortcuts.entries.next.key,
() => {
const data = dataRef.current
const currentActiveEntryIndex = data.indexOf(
currentEntryIdRef.current || "",
)
const nextIndex = Math.min(currentActiveEntryIndex + 1, data.length - 1)
virtuosoRef.current?.scrollIntoView?.({
index: nextIndex,
})
const nextId = data![nextIndex]
navigate({
entryId: nextId,
})
},
{ scopes: ["home"], enabled: enabledArrowKey },
)
useHotkeys(
shortcuts.entries.previous.key,
() => {
const data = dataRef.current
const currentActiveEntryIndex = data.indexOf(
currentEntryIdRef.current || "",
)
const nextIndex =
currentActiveEntryIndex === -1 ?
data.length - 1 :
Math.max(0, currentActiveEntryIndex - 1)
virtuosoRef.current?.scrollIntoView?.({
index: nextIndex,
})
const nextId = data![nextIndex]
navigate({
entryId: nextId,
})
},
{ scopes: ["home"], enabled: enabledArrowKey },
)
return null
})

View File

@ -1,6 +1,5 @@
import { useGeneralSettingKey } from "@renderer/atoms/settings/general"
import { views } from "@renderer/constants"
import { shortcuts } from "@renderer/constants/shortcuts"
import {
useRouteParamsSelector,
useRouteParms,
@ -10,7 +9,6 @@ import { entries, useEntries } from "@renderer/queries/entries"
import { entryActions, useEntryIdsByFeedIdOrView } from "@renderer/store/entry"
import { useFolderFeedsByFeedId } from "@renderer/store/subscription"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useHotkeys } from "react-hotkeys-hook"
import type { ListRange } from "react-virtuoso"
import { useDebounceCallback } from "usehooks-ts"
@ -125,14 +123,6 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => {
const entryIds = remoteEntryIds || currentEntries
useHotkeys(
shortcuts.entries.refetch.key,
() => {
query.refetch()
},
{ scopes: ["home"] },
)
// in unread only entries only can grow the data, but not shrink
// so we memo this previous data to avoid the flicker
const prevEntryIdsRef = useRef(entryIds)

View File

@ -1,4 +1,3 @@
import { useMainContainerElement } from "@renderer/atoms/dom"
import {
setGeneralSetting,
useGeneralSettingKey,
@ -27,10 +26,8 @@ import {
import { shortcuts } from "@renderer/constants/shortcuts"
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
import {
useRouteEntryId,
useRouteParms,
} from "@renderer/hooks/biz/useRouteParams"
import { useRefValue } from "@renderer/hooks/common"
import { useIsOnline } from "@renderer/hooks/common/useIsOnline"
import { apiClient } from "@renderer/lib/api-fetch"
import { cn, getEntriesParams, getOS, isBizId } from "@renderer/lib/utils"
@ -48,11 +45,9 @@ import {
forwardRef,
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react"
import { useHotkeys } from "react-hotkeys-hook"
import type {
ScrollSeekConfiguration,
VirtuosoHandle,
@ -60,6 +55,7 @@ import type {
} from "react-virtuoso"
import { Virtuoso, VirtuosoGrid } from "react-virtuoso"
import { EntryColumnShortcutHandler } from "./EntryColumnShortcutHandler"
import { useEntriesByView, useEntryMarkReadHandler } from "./hooks"
import {
EntryItem,
@ -204,7 +200,11 @@ export function EntryColumn() {
/>
) :
(
<EntryList {...virtuosoOptions} virtuosoRef={virtuosoRef} />
<EntryList
{...virtuosoOptions}
virtuosoRef={virtuosoRef}
refetch={entries.refetch}
/>
)}
</ScrollArea.ScrollArea>
</m.div>
@ -430,81 +430,10 @@ const EmptyList = forwardRef<HTMLDivElement, HTMLMotionProps<"div">>(
const EntryList: FC<
VirtuosoProps<string, unknown> & {
virtuosoRef: React.RefObject<VirtuosoHandle>
refetch: () => void
}
> = ({ virtuosoRef, ...virtuosoOptions }) => {
const dataRef = useRefValue(virtuosoOptions.data!)
const currentEntryIdRef = useRefValue(useRouteEntryId())
const navigate = useNavigateEntry()
const $mainContainer = useMainContainerElement()
const [enabledArrowKey, setEnabledArrowKey] = useState(false)
// Enable arrow key navigation shortcuts only when focus is on entryContent or entryList,
// entryList shortcuts should not be triggered in the feed col
useLayoutEffect(() => {
if (!$mainContainer) return
const handler = () => {
const target = document.activeElement
const isFocusIn =
$mainContainer.contains(target) || $mainContainer === target
setEnabledArrowKey(isFocusIn)
}
handler()
// NOTE: focusin event will bubble to the document
document.addEventListener("focusin", handler)
return () => {
document.removeEventListener("focusin", handler)
}
}, [$mainContainer])
useHotkeys(
shortcuts.entries.next.key,
() => {
const data = dataRef.current
const currentActiveEntryIndex = data.indexOf(
currentEntryIdRef.current || "",
)
const nextIndex = Math.min(currentActiveEntryIndex + 1, data.length - 1)
virtuosoRef.current?.scrollIntoView?.({
index: nextIndex,
})
const nextId = data![nextIndex]
navigate({
entryId: nextId,
})
},
{ scopes: ["home"], enabled: enabledArrowKey },
)
useHotkeys(
shortcuts.entries.previous.key,
() => {
const data = dataRef.current
const currentActiveEntryIndex = data.indexOf(
currentEntryIdRef.current || "",
)
const nextIndex =
currentActiveEntryIndex === -1 ?
data.length - 1 :
Math.max(0, currentActiveEntryIndex - 1)
virtuosoRef.current?.scrollIntoView?.({
index: nextIndex,
})
const nextId = data![nextIndex]
navigate({
entryId: nextId,
})
},
{ scopes: ["home"], enabled: enabledArrowKey },
)
> = ({ virtuosoRef, refetch, ...virtuosoOptions }) => {
// Prevent scroll list move when press up/down key, the up/down key should be taken over by the shortcut key we defined.
const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = useCallback(
(e) => {
@ -515,10 +444,17 @@ const EntryList: FC<
[],
)
return (
<Virtuoso
onKeyDown={handleKeyDown}
{...virtuosoOptions}
ref={virtuosoRef}
/>
<>
<Virtuoso
onKeyDown={handleKeyDown}
{...virtuosoOptions}
ref={virtuosoRef}
/>
<EntryColumnShortcutHandler
refetch={refetch}
data={virtuosoOptions.data!}
virtuosoRef={virtuosoRef}
/>
</>
)
}