use a single call to useContextMenu as recommended in the docs

This commit is contained in:
Athou
2023-06-26 20:04:51 +02:00
parent 95f4cffa7c
commit 3e903fc6bc
5 changed files with 35 additions and 46 deletions

View File

@@ -95,6 +95,7 @@ export const Constants = {
}, },
dom: { dom: {
entryId: (entry: Entry) => `entry-id-${entry.id}`, entryId: (entry: Entry) => `entry-id-${entry.id}`,
entryContextMenuId: (entry: Entry) => entry.id,
}, },
browserExtensionUrl: "https://github.com/Athou/commafeed-browser-extension", browserExtensionUrl: "https://github.com/Athou/commafeed-browser-extension",
bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e", bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e",

View File

@@ -30,17 +30,16 @@ export const calculatePlaceholderSize = ({ width, height, maxWidth }: { width?:
export const scrollToWithCallback = ({ options, onScrollEnded }: { options: ScrollToOptions; onScrollEnded: () => void }) => { export const scrollToWithCallback = ({ options, onScrollEnded }: { options: ScrollToOptions; onScrollEnded: () => void }) => {
const offset = (options.top ?? 0).toFixed() const offset = (options.top ?? 0).toFixed()
const onScroll = () => { const onScroll = throttle(100, () => {
if (window.scrollY.toFixed() === offset) { if (window.scrollY.toFixed() === offset) {
window.removeEventListener("scroll", throttleListener) window.removeEventListener("scroll", onScroll)
onScrollEnded() onScrollEnded()
} }
} })
const throttleListener = throttle(100, onScroll) window.addEventListener("scroll", onScroll)
window.addEventListener("scroll", throttleListener)
// scrollTo does not trigger if there's nothing to do, trigger it manually // scrollTo does not trigger if there's nothing to do, trigger it manually
throttleListener() onScroll()
window.scrollTo(options) window.scrollTo(options)
} }

View File

@@ -20,6 +20,7 @@ import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMousetrap } from "hooks/useMousetrap" import { useMousetrap } from "hooks/useMousetrap"
import { useViewMode } from "hooks/useViewMode" import { useViewMode } from "hooks/useViewMode"
import { useEffect } from "react" import { useEffect } from "react"
import { useContextMenu } from "react-contexify"
import InfiniteScroll from "react-infinite-scroller" import InfiniteScroll from "react-infinite-scroller"
import { throttle } from "throttle-debounce" import { throttle } from "throttle-debounce"
import { FeedEntry } from "./FeedEntry" import { FeedEntry } from "./FeedEntry"
@@ -59,6 +60,15 @@ export function FeedEntries() {
} }
} }
const contextMenu = useContextMenu()
const headerRightClicked = (entry: ExpendableEntry, event: React.MouseEvent) => {
event.preventDefault()
contextMenu.show({
id: Constants.dom.entryContextMenuId(entry),
event,
})
}
const bodyClicked = (entry: ExpendableEntry) => { const bodyClicked = (entry: ExpendableEntry) => {
if (viewMode !== "expanded") return if (viewMode !== "expanded") return
dispatch( dispatch(
@@ -73,8 +83,15 @@ export function FeedEntries() {
const swipedRight = (entry: ExpendableEntry) => dispatch(markEntry({ entry, read: !entry.read })) const swipedRight = (entry: ExpendableEntry) => dispatch(markEntry({ entry, read: !entry.read }))
// close context menu on scroll
useEffect(() => { useEffect(() => {
const listener = () => { const listener = throttle(100, () => contextMenu.hideAll())
window.addEventListener("scroll", listener)
return () => window.removeEventListener("scroll", listener)
}, [contextMenu])
useEffect(() => {
const listener = throttle(100, () => {
if (viewMode !== "expanded") return if (viewMode !== "expanded") return
if (scrollingToEntry) return if (scrollingToEntry) return
@@ -96,11 +113,10 @@ export function FeedEntries() {
}) })
) )
} }
} })
const throttledListener = throttle(100, listener) window.addEventListener("scroll", listener)
window.addEventListener("scroll", throttledListener) return () => window.removeEventListener("scroll", listener)
return () => window.removeEventListener("scroll", throttledListener) }, [dispatch, contextMenu, entries, viewMode, scrollMarks, scrollingToEntry])
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
useMousetrap("r", () => dispatch(reloadEntries())) useMousetrap("r", () => dispatch(reloadEntries()))
useMousetrap("j", () => useMousetrap("j", () =>
@@ -278,6 +294,7 @@ export function FeedEntries() {
showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")} showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")}
maxWidth={sidebarVisible ? Constants.layout.entryMaxWidth : undefined} maxWidth={sidebarVisible ? Constants.layout.entryMaxWidth : undefined}
onHeaderClick={event => headerClicked(entry, event)} onHeaderClick={event => headerClicked(entry, event)}
onHeaderRightClick={event => headerRightClicked(entry, event)}
onBodyClick={() => bodyClicked(entry)} onBodyClick={() => bodyClicked(entry)}
onSwipedRight={() => swipedRight(entry)} onSwipedRight={() => swipedRight(entry)}
/> />

View File

@@ -7,7 +7,7 @@ import React from "react"
import { useSwipeable } from "react-swipeable" import { useSwipeable } from "react-swipeable"
import { FeedEntryBody } from "./FeedEntryBody" import { FeedEntryBody } from "./FeedEntryBody"
import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader" import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader"
import { FeedEntryContextMenu, useFeedEntryContextMenu } from "./FeedEntryContextMenu" import { FeedEntryContextMenu } from "./FeedEntryContextMenu"
import { FeedEntryFooter } from "./FeedEntryFooter" import { FeedEntryFooter } from "./FeedEntryFooter"
import { FeedEntryHeader } from "./FeedEntryHeader" import { FeedEntryHeader } from "./FeedEntryHeader"
@@ -18,6 +18,7 @@ interface FeedEntryProps {
showSelectionIndicator: boolean showSelectionIndicator: boolean
maxWidth?: number maxWidth?: number
onHeaderClick: (e: React.MouseEvent) => void onHeaderClick: (e: React.MouseEvent) => void
onHeaderRightClick: (e: React.MouseEvent) => void
onBodyClick: (e: React.MouseEvent) => void onBodyClick: (e: React.MouseEvent) => void
onSwipedRight: () => void onSwipedRight: () => void
} }
@@ -90,8 +91,6 @@ export function FeedEntry(props: FeedEntryProps) {
onSwipedRight: props.onSwipedRight, onSwipedRight: props.onSwipedRight,
}) })
const { onContextMenu } = useFeedEntryContextMenu(props.entry)
let paddingX: MantineNumberSize = "xs" let paddingX: MantineNumberSize = "xs"
if (viewMode === "title" || viewMode === "cozy") paddingX = 6 if (viewMode === "title" || viewMode === "cozy") paddingX = 6
@@ -129,7 +128,7 @@ export function FeedEntry(props: FeedEntryProps) {
rel="noreferrer" rel="noreferrer"
onClick={props.onHeaderClick} onClick={props.onHeaderClick}
onAuxClick={props.onHeaderClick} onAuxClick={props.onHeaderClick}
onContextMenu={onContextMenu} onContextMenu={props.onHeaderRightClick}
> >
<Box px={paddingX} py={paddingY} {...swipeHandlers}> <Box px={paddingX} py={paddingY} {...swipeHandlers}>
{compactHeader && <FeedEntryCompactHeader entry={props.entry} />} {compactHeader && <FeedEntryCompactHeader entry={props.entry} />}

View File

@@ -1,15 +1,14 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { createStyles, Group } from "@mantine/core" import { createStyles, Group } from "@mantine/core"
import { Constants } from "app/constants"
import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries" import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
import { redirectToFeed } from "app/slices/redirect" import { redirectToFeed } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types" import { Entry } from "app/types"
import { truncate } from "app/utils" import { truncate } from "app/utils"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useEffect } from "react" import { Item, Menu, Separator } from "react-contexify"
import { Item, Menu, Separator, useContextMenu } from "react-contexify"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
import { throttle } from "throttle-debounce"
interface FeedEntryContextMenuProps { interface FeedEntryContextMenuProps {
entry: Entry entry: Entry
@@ -28,8 +27,6 @@ const useStyles = createStyles(theme => ({
}, },
})) }))
const menuId = (entry: Entry) => entry.id
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) { export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const { classes, theme } = useStyles() const { classes, theme } = useStyles()
const sourceType = useAppSelector(state => state.entries.source.type) const sourceType = useAppSelector(state => state.entries.source.type)
@@ -37,7 +34,7 @@ export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const { openLinkInBackgroundTab } = useBrowserExtension() const { openLinkInBackgroundTab } = useBrowserExtension()
return ( return (
<Menu id={menuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}> <Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}>
<Item <Item
onClick={() => { onClick={() => {
window.open(props.entry.url, "_blank", "noreferrer") window.open(props.entry.url, "_blank", "noreferrer")
@@ -101,27 +98,3 @@ export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
</Menu> </Menu>
) )
} }
export function useFeedEntryContextMenu(entry: Entry) {
const contextMenu = useContextMenu({
id: menuId(entry),
})
const onContextMenu = (event: React.MouseEvent) => {
event.preventDefault()
contextMenu.show({
event,
})
}
// close context menu on scroll
useEffect(() => {
const listener = () => contextMenu.hideAll()
const throttledListener = throttle(100, listener)
window.addEventListener("scroll", throttledListener)
return () => window.removeEventListener("scroll", throttledListener)
}, [contextMenu])
return { onContextMenu }
}