From 41f00d5767035b35d4d1dd9bbbbb5336747ced06 Mon Sep 17 00:00:00 2001 From: Chowdhury Foysal Ahamed Date: Mon, 18 Sep 2023 17:04:19 +0200 Subject: [PATCH 1/5] :bug: Handle middle mouse click on feed list items --- src/view/com/posts/Feed.tsx | 40 ++++++++++++++++++++++++++++++++++++- src/view/com/util/Link.tsx | 16 ++++++++++----- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 55e69a318d..40ee80a686 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -19,6 +19,7 @@ import {s} from 'lib/styles' import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' +import {isWeb} from 'platform/detection' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -54,6 +55,7 @@ export const Feed = observer(function Feed({ const theme = useTheme() const {track} = useAnalytics() const [isRefreshing, setIsRefreshing] = React.useState(false) + const listWrapperRef = React.useRef(null) const data = React.useMemo(() => { let feedItems: any[] = [] @@ -152,8 +154,44 @@ export const Feed = observer(function Feed({ [feed], ) + // This is the handler for the middle mouse button click on the feed. + // Normally, we would do this via `onAuxClick` handler on each link element + // However, that handler is not supported on react-native-web and there are some + // discrepancies between various browsers (i.e: safari doesn't trigger it and routes through click event) + // So, this temporary alternative is meant to bridge the gap in an efficient way until the support improves. + React.useEffect(() => { + if (listWrapperRef?.current && isWeb) { + const wrapperEl = listWrapperRef.current + const handleAuxClick = (e: MouseEvent & {target: HTMLElement}) => { + // Only handle the middle mouse button click, early exit otherwise + if (e.button !== 1) return + + // Each feed item is wrapped by a div with a data-href attribute + // The value of the attr contains the link to the post + // Maybe this needs a better selector? in case there are nested items with links? + const parentWithPostLink = e.target.closest?.('div[data-href]') + + // Only try to process the click if we found a parent with the data-href attr and there is a value for it + console.log(parentWithPostLink) + if (parentWithPostLink) { + const href = parentWithPostLink.getAttribute('data-href') + console.log(parentWithPostLink, href) + if (href) window.open(href, '_blank') + } + } + + // @ts-ignore For web only + wrapperEl.addEventListener('auxclick', handleAuxClick) + + return () => { + // @ts-ignore For web only + wrapperEl?.removeEventListener('auxclick', handleAuxClick) + } + } + }, []) + return ( - + () + const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const onPress = React.useCallback( (e?: Event) => { @@ -94,16 +95,21 @@ export const Link = observer(function Link({ accessibilityRole="link" {...props}> {/* @ts-ignore web only -prf */} - + {children ? children : {title || 'link'}} ) } + // @ts-ignore web only -prf + props.dataSet = props.dataSet || {} + if (isWeb) { + // @ts-ignore web only + props.dataSet.href = href + } + if (anchorNoUnderline) { - // @ts-ignore web only -prf - props.dataSet = props.dataSet || {} // @ts-ignore web only -prf props.dataSet.noUnderline = 1 } @@ -120,7 +126,7 @@ export const Link = observer(function Link({ accessible={accessible} accessibilityRole="link" // @ts-ignore web only -prf - href={asAnchor ? sanitizeUrl(href) : undefined} + href={anchorHref} {...props}> {children ? children : {title || 'link'}} From f87ff54bc3f87c9bc254bd8b51d9448791a2b5d7 Mon Sep 17 00:00:00 2001 From: Chowdhury Foysal Ahamed Date: Mon, 18 Sep 2023 21:17:38 +0200 Subject: [PATCH 2/5] :recycle: Refactor the event listener and turn it into a dedicated hook for web --- src/lib/hooks/useAuxClick.web.ts | 43 ++++++++++++++++++++++++++++++++ src/view/com/posts/Feed.tsx | 40 +---------------------------- src/view/shell/index.web.tsx | 2 ++ 3 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 src/lib/hooks/useAuxClick.web.ts diff --git a/src/lib/hooks/useAuxClick.web.ts b/src/lib/hooks/useAuxClick.web.ts new file mode 100644 index 0000000000..ca98116159 --- /dev/null +++ b/src/lib/hooks/useAuxClick.web.ts @@ -0,0 +1,43 @@ +import {useEffect} from 'react' + +// This is the handler for the middle mouse button click on the feed. +// Normally, we would do this via `onAuxClick` handler on each link element +// However, that handler is not supported on react-native-web and there are some +// discrepancies between various browsers (i.e: safari doesn't trigger it and routes through click event) +// So, this temporary alternative is meant to bridge the gap in an efficient way until the support improves. +export const useAuxClick = () => { + const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + useEffect(() => { + // On the web, it should always be there but in case it gets accidentally included in native builds + const wrapperEl = document?.body + + // Safari already handles auxclick event as click+metaKey so we need to avoid doing this there in case it becomes recursive + if (wrapperEl && !isSafari) { + const handleAuxClick = (e: MouseEvent & {target: HTMLElement}) => { + // Only handle the middle mouse button click + // Only handle if the clicked element itself or one of its ancestors is a link + if ( + e.button !== 1 || + e.target.closest('a') || + e.target.tagName === 'A' + ) { + return + } + + // On the original element, trigger a click event with metaKey set to true so that it triggers + // the browser's default behavior of opening the link in a new tab + e.target.dispatchEvent( + new MouseEvent('click', {metaKey: true, bubbles: true}), + ) + } + + // @ts-ignore For web only + wrapperEl.addEventListener('auxclick', handleAuxClick) + + return () => { + // @ts-ignore For web only + wrapperEl?.removeEventListener('auxclick', handleAuxClick) + } + } + }, [isSafari]) +} diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 40ee80a686..55e69a318d 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -19,7 +19,6 @@ import {s} from 'lib/styles' import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' -import {isWeb} from 'platform/detection' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -55,7 +54,6 @@ export const Feed = observer(function Feed({ const theme = useTheme() const {track} = useAnalytics() const [isRefreshing, setIsRefreshing] = React.useState(false) - const listWrapperRef = React.useRef(null) const data = React.useMemo(() => { let feedItems: any[] = [] @@ -154,44 +152,8 @@ export const Feed = observer(function Feed({ [feed], ) - // This is the handler for the middle mouse button click on the feed. - // Normally, we would do this via `onAuxClick` handler on each link element - // However, that handler is not supported on react-native-web and there are some - // discrepancies between various browsers (i.e: safari doesn't trigger it and routes through click event) - // So, this temporary alternative is meant to bridge the gap in an efficient way until the support improves. - React.useEffect(() => { - if (listWrapperRef?.current && isWeb) { - const wrapperEl = listWrapperRef.current - const handleAuxClick = (e: MouseEvent & {target: HTMLElement}) => { - // Only handle the middle mouse button click, early exit otherwise - if (e.button !== 1) return - - // Each feed item is wrapped by a div with a data-href attribute - // The value of the attr contains the link to the post - // Maybe this needs a better selector? in case there are nested items with links? - const parentWithPostLink = e.target.closest?.('div[data-href]') - - // Only try to process the click if we found a parent with the data-href attr and there is a value for it - console.log(parentWithPostLink) - if (parentWithPostLink) { - const href = parentWithPostLink.getAttribute('data-href') - console.log(parentWithPostLink, href) - if (href) window.open(href, '_blank') - } - } - - // @ts-ignore For web only - wrapperEl.addEventListener('auxclick', handleAuxClick) - - return () => { - // @ts-ignore For web only - wrapperEl?.removeEventListener('auxclick', handleAuxClick) - } - } - }, []) - return ( - + () + useAuxClick() useEffect(() => { navigator.addListener('state', () => { From 04bcdf1bf9c4864faa0adde6e9cb93b83acecef4 Mon Sep 17 00:00:00 2001 From: Chowdhury Foysal Ahamed Date: Mon, 18 Sep 2023 21:20:39 +0200 Subject: [PATCH 3/5] :broom: Cleanup unnecessary Link changes --- src/view/com/util/Link.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index a4162a8d04..de8d51f322 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -24,7 +24,7 @@ import {NavigationProp} from 'lib/routes/types' import {router} from '../../../routes' import {useStores, RootStoreModel} from 'state/index' import {convertBskyAppUrlIfNeeded, isExternalUrl} from 'lib/strings/url-helpers' -import {isAndroid, isDesktopWeb, isWeb} from 'platform/detection' +import {isAndroid, isDesktopWeb} from 'platform/detection' import {sanitizeUrl} from '@braintree/sanitize-url' import FixedTouchableHighlight from '../pager/FixedTouchableHighlight' @@ -95,21 +95,16 @@ export const Link = observer(function Link({ accessibilityRole="link" {...props}> {/* @ts-ignore web only -prf */} - + {children ? children : {title || 'link'}} ) } - // @ts-ignore web only -prf - props.dataSet = props.dataSet || {} - if (isWeb) { - // @ts-ignore web only - props.dataSet.href = href - } - if (anchorNoUnderline) { + // @ts-ignore web only -prf + props.dataSet = props.dataSet || {} // @ts-ignore web only -prf props.dataSet.noUnderline = 1 } From 9a835568eda3406c32195cfb187aa97b1e6cf096 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 18 Sep 2023 13:41:41 -0700 Subject: [PATCH 4/5] Fix import --- src/view/shell/index.web.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 8c4c5d3633..67f9888448 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -16,7 +16,7 @@ import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' import {BottomBarWeb} from './bottom-bar/BottomBarWeb' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' -import {useAuxClick} from 'lib/hooks/useAuxClick.web' +import {useAuxClick} from 'lib/hooks/useAuxClick' const ShellInner = observer(function ShellInnerImpl() { const store = useStores() From e01009219da65bb4b66d91ddf1930e49aeef6261 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 18 Sep 2023 14:31:02 -0700 Subject: [PATCH 5/5] Create native version of useAuxClick --- src/lib/hooks/useAuxClick.ts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/lib/hooks/useAuxClick.ts diff --git a/src/lib/hooks/useAuxClick.ts b/src/lib/hooks/useAuxClick.ts new file mode 100644 index 0000000000..ab6fd4365c --- /dev/null +++ b/src/lib/hooks/useAuxClick.ts @@ -0,0 +1,2 @@ +// does nothing in native +export const useAuxClick = () => {}