about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/hooks/useAuxClick.ts2
-rw-r--r--src/lib/hooks/useAuxClick.web.ts43
-rw-r--r--src/view/com/util/Link.tsx5
-rw-r--r--src/view/shell/index.web.tsx2
4 files changed, 50 insertions, 2 deletions
diff --git a/src/lib/hooks/useAuxClick.ts b/src/lib/hooks/useAuxClick.ts
new file mode 100644
index 000000000..ab6fd4365
--- /dev/null
+++ b/src/lib/hooks/useAuxClick.ts
@@ -0,0 +1,2 @@
+// does nothing in native
+export const useAuxClick = () => {}
diff --git a/src/lib/hooks/useAuxClick.web.ts b/src/lib/hooks/useAuxClick.web.ts
new file mode 100644
index 000000000..ca9811615
--- /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/util/Link.tsx b/src/view/com/util/Link.tsx
index d4df2bec4..1a6f047f6 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -59,6 +59,7 @@ export const Link = observer(function Link({
 }: Props) {
   const store = useStores()
   const navigation = useNavigation<NavigationProp>()
+  const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
 
   const onPress = React.useCallback(
     (e?: Event) => {
@@ -96,7 +97,7 @@ export const Link = observer(function Link({
         accessibilityRole="link"
         {...props}>
         {/* @ts-ignore web only -prf */}
-        <View style={style} href={asAnchor ? sanitizeUrl(href) : undefined}>
+        <View style={style} href={anchorHref}>
           {children ? children : <Text>{title || 'link'}</Text>}
         </View>
       </TouchableWithoutFeedback>
@@ -123,7 +124,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 : <Text>{title || 'link'}</Text>}
     </Com>
diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx
index 124341917..67f988844 100644
--- a/src/view/shell/index.web.tsx
+++ b/src/view/shell/index.web.tsx
@@ -16,11 +16,13 @@ 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'
 
 const ShellInner = observer(function ShellInnerImpl() {
   const store = useStores()
   const {isDesktop, isMobile} = useWebMediaQueries()
   const navigator = useNavigation<NavigationProp>()
+  useAuxClick()
 
   useEffect(() => {
     navigator.addListener('state', () => {