about summary refs log tree commit diff
path: root/src/view/com/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util')
-rw-r--r--src/view/com/util/Link.tsx25
-rw-r--r--src/view/com/util/WebAuxClickWrapper.tsx30
-rw-r--r--src/view/com/util/forms/Button.tsx12
-rw-r--r--src/view/com/util/forms/DropdownButton.tsx40
-rw-r--r--src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx8
-rw-r--r--src/view/com/util/post-embeds/QuoteEmbed.tsx16
-rw-r--r--src/view/com/util/post-embeds/index.tsx17
7 files changed, 111 insertions, 37 deletions
diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx
index a517ba430..afbdeb8f4 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -31,6 +31,7 @@ import {PressableWithHover} from './PressableWithHover'
 import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
 import {useModalControls} from '#/state/modals'
 import {useOpenLink} from '#/state/preferences/in-app-browser'
+import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper'
 
 type Event =
   | React.MouseEvent<HTMLAnchorElement, MouseEvent>
@@ -104,17 +105,19 @@ export const Link = memo(function Link({
       )
     }
     return (
-      <TouchableWithoutFeedback
-        testID={testID}
-        onPress={onPress}
-        accessible={accessible}
-        accessibilityRole="link"
-        {...props}>
-        {/* @ts-ignore web only -prf */}
-        <View style={style} href={anchorHref}>
-          {children ? children : <Text>{title || 'link'}</Text>}
-        </View>
-      </TouchableWithoutFeedback>
+      <WebAuxClickWrapper>
+        <TouchableWithoutFeedback
+          testID={testID}
+          onPress={onPress}
+          accessible={accessible}
+          accessibilityRole="link"
+          {...props}>
+          {/* @ts-ignore web only -prf */}
+          <View style={style} href={anchorHref}>
+            {children ? children : <Text>{title || 'link'}</Text>}
+          </View>
+        </TouchableWithoutFeedback>
+      </WebAuxClickWrapper>
     )
   }
 
diff --git a/src/view/com/util/WebAuxClickWrapper.tsx b/src/view/com/util/WebAuxClickWrapper.tsx
new file mode 100644
index 000000000..8105a8518
--- /dev/null
+++ b/src/view/com/util/WebAuxClickWrapper.tsx
@@ -0,0 +1,30 @@
+import React from 'react'
+import {Platform} from 'react-native'
+
+const onMouseUp = (e: React.MouseEvent & {target: HTMLElement}) => {
+  // Only handle whenever it is the middle button
+  if (e.button !== 1 || e.target.closest('a') || e.target.tagName === 'A') {
+    return
+  }
+
+  e.target.dispatchEvent(
+    new MouseEvent('click', {metaKey: true, bubbles: true}),
+  )
+}
+
+const onMouseDown = (e: React.MouseEvent) => {
+  // Prevents the middle click scroll from enabling
+  if (e.button !== 1) return
+  e.preventDefault()
+}
+
+export function WebAuxClickWrapper({children}: React.PropsWithChildren<{}>) {
+  if (Platform.OS !== 'web') return children
+
+  return (
+    // @ts-ignore web only
+    <div onMouseDown={onMouseDown} onMouseUp={onMouseUp}>
+      {children}
+    </div>
+  )
+}
diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx
index 8f24f8288..e6e05bb04 100644
--- a/src/view/com/util/forms/Button.tsx
+++ b/src/view/com/util/forms/Button.tsx
@@ -9,15 +9,13 @@ import {
   PressableStateCallbackType,
   ActivityIndicator,
   View,
+  NativeSyntheticEvent,
+  NativeTouchEvent,
 } from 'react-native'
 import {Text} from '../text/Text'
 import {useTheme} from 'lib/ThemeContext'
 import {choose} from 'lib/functions'
 
-type Event =
-  | React.MouseEvent<HTMLAnchorElement, MouseEvent>
-  | GestureResponderEvent
-
 export type ButtonType =
   | 'primary'
   | 'secondary'
@@ -59,7 +57,7 @@ export function Button({
   style?: StyleProp<ViewStyle>
   labelContainerStyle?: StyleProp<ViewStyle>
   labelStyle?: StyleProp<TextStyle>
-  onPress?: () => void | Promise<void>
+  onPress?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void | Promise<void>
   testID?: string
   accessibilityLabel?: string
   accessibilityHint?: string
@@ -148,11 +146,11 @@ export function Button({
 
   const [isLoading, setIsLoading] = React.useState(false)
   const onPressWrapped = React.useCallback(
-    async (event: Event) => {
+    async (event: GestureResponderEvent) => {
       event.stopPropagation()
       event.preventDefault()
       withLoading && setIsLoading(true)
-      await onPress?.()
+      await onPress?.(event)
       withLoading && setIsLoading(false)
     },
     [onPress, withLoading],
diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx
index 411b77484..2285b0615 100644
--- a/src/view/com/util/forms/DropdownButton.tsx
+++ b/src/view/com/util/forms/DropdownButton.tsx
@@ -1,10 +1,12 @@
 import React, {PropsWithChildren, useMemo, useRef} from 'react'
 import {
   Dimensions,
+  GestureResponderEvent,
   StyleProp,
   StyleSheet,
   TouchableOpacity,
   TouchableWithoutFeedback,
+  useWindowDimensions,
   View,
   ViewStyle,
 } from 'react-native'
@@ -19,6 +21,7 @@ import {useTheme} from 'lib/ThemeContext'
 import {HITSLOP_10} from 'lib/constants'
 import {useLingui} from '@lingui/react'
 import {msg} from '@lingui/macro'
+import {isWeb} from 'platform/detection'
 
 const ESTIMATED_BTN_HEIGHT = 50
 const ESTIMATED_SEP_HEIGHT = 16
@@ -80,21 +83,22 @@ export function DropdownButton({
   const ref1 = useRef<TouchableOpacity>(null)
   const ref2 = useRef<View>(null)
 
-  const onPress = () => {
+  const onPress = (e: GestureResponderEvent) => {
     const ref = ref1.current || ref2.current
+    const {height: winHeight} = Dimensions.get('window')
+    const pressY = e.nativeEvent.pageY
     ref?.measure(
       (
         _x: number,
         _y: number,
         width: number,
-        height: number,
+        _height: number,
         pageX: number,
         pageY: number,
       ) => {
         if (!menuWidth) {
           menuWidth = 200
         }
-        const winHeight = Dimensions.get('window').height
         let estimatedMenuHeight = 0
         for (const item of items) {
           if (item && isSep(item)) {
@@ -108,13 +112,16 @@ export function DropdownButton({
         const newX = openToRight
           ? pageX + width + rightOffset
           : pageX + width - menuWidth
-        let newY = pageY + height + bottomOffset
+
+        // Add a bit of additional room
+        let newY = pressY + bottomOffset + 20
         if (openUpwards || newY + estimatedMenuHeight > winHeight) {
           newY -= estimatedMenuHeight
         }
         createDropdownMenu(
           newX,
           newY,
+          pageY,
           menuWidth,
           items.filter(v => !!v) as DropdownItem[],
         )
@@ -168,6 +175,7 @@ export function DropdownButton({
 function createDropdownMenu(
   x: number,
   y: number,
+  pageY: number,
   width: number,
   items: DropdownItem[],
 ): RootSiblings {
@@ -185,6 +193,7 @@ function createDropdownMenu(
         onOuterPress={onOuterPress}
         x={x}
         y={y}
+        pageY={pageY}
         width={width}
         items={items}
         onPressItem={onPressItem}
@@ -198,6 +207,7 @@ type DropDownItemProps = {
   onOuterPress: () => void
   x: number
   y: number
+  pageY: number
   width: number
   items: DropdownItem[]
   onPressItem: (index: number) => void
@@ -207,6 +217,7 @@ const DropdownItems = ({
   onOuterPress,
   x,
   y,
+  pageY,
   width,
   items,
   onPressItem,
@@ -214,6 +225,7 @@ const DropdownItems = ({
   const pal = usePalette('default')
   const theme = useTheme()
   const {_} = useLingui()
+  const {height: screenHeight} = useWindowDimensions()
   const dropDownBackgroundColor =
     theme.colorScheme === 'dark' ? pal.btn : pal.view
   const separatorColor =
@@ -233,7 +245,21 @@ const DropdownItems = ({
         onPress={onOuterPress}
         accessibilityLabel={_(msg`Toggle dropdown`)}
         accessibilityHint="">
-        <View style={[styles.bg]} />
+        <View
+          style={[
+            styles.bg,
+            // On web we need to adjust the top and bottom relative to the scroll position
+            isWeb
+              ? {
+                  top: -pageY,
+                  bottom: pageY - screenHeight,
+                }
+              : {
+                  top: 0,
+                  bottom: 0,
+                },
+          ]}
+        />
       </TouchableWithoutFeedback>
       <View
         style={[
@@ -295,10 +321,8 @@ function isBtn(item: DropdownItem): item is DropdownItemButton {
 const styles = StyleSheet.create({
   bg: {
     position: 'absolute',
-    top: 0,
-    right: 0,
-    bottom: 0,
     left: 0,
+    width: '100%',
     backgroundColor: '#000',
     opacity: 0.1,
   },
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
index 8b0858b69..d556e7669 100644
--- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
@@ -78,9 +78,13 @@ function Player({
   onLoad: () => void
 }) {
   // ensures we only load what's requested
+  // when it's a youtube video, we need to allow both bsky.app and youtube.com
   const onShouldStartLoadWithRequest = React.useCallback(
-    (event: ShouldStartLoadRequest) => event.url === params.playerUri,
-    [params.playerUri],
+    (event: ShouldStartLoadRequest) =>
+      event.url === params.playerUri ||
+      (params.source.startsWith('youtube') &&
+        event.url.includes('www.youtube.com')),
+    [params.playerUri, params.source],
   )
 
   // Don't show the player until it is active
diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx
index 256817bba..d9d84feb4 100644
--- a/src/view/com/util/post-embeds/QuoteEmbed.tsx
+++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx
@@ -113,13 +113,15 @@ export function QuoteEmbed({
       hoverStyle={{borderColor: pal.colors.borderLinkHover}}
       href={itemHref}
       title={itemTitle}>
-      <PostMeta
-        author={quote.author}
-        showAvatar
-        authorHasWarning={false}
-        postHref={itemHref}
-        timestamp={quote.indexedAt}
-      />
+      <View pointerEvents="none">
+        <PostMeta
+          author={quote.author}
+          showAvatar
+          authorHasWarning={false}
+          postHref={itemHref}
+          timestamp={quote.indexedAt}
+        />
+      </View>
       {moderation ? (
         <PostAlerts moderation={moderation} style={styles.alert} />
       ) : null}
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index 6f168a293..7e235babb 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, {useCallback} from 'react'
 import {
   StyleSheet,
   StyleProp,
@@ -29,6 +29,8 @@ import {ListEmbed} from './ListEmbed'
 import {isCauseALabelOnUri, isQuoteBlurred} from 'lib/moderation'
 import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
 import {ContentHider} from '../moderation/ContentHider'
+import {isNative} from '#/platform/detection'
+import {shareUrl} from '#/lib/sharing'
 
 type Embed =
   | AppBskyEmbedRecord.View
@@ -51,6 +53,16 @@ export function PostEmbeds({
   const pal = usePalette('default')
   const {openLightbox} = useLightboxControls()
 
+  const externalUri = AppBskyEmbedExternal.isView(embed)
+    ? embed.external.uri
+    : null
+
+  const onShareExternal = useCallback(() => {
+    if (externalUri && isNative) {
+      shareUrl(externalUri)
+    }
+  }, [externalUri])
+
   // quote post with media
   // =
   if (AppBskyEmbedRecordWithMedia.isView(embed)) {
@@ -164,7 +176,8 @@ export function PostEmbeds({
         anchorNoUnderline
         href={link.uri}
         style={[styles.extOuter, pal.view, pal.borderDark, style]}
-        hoverStyle={{borderColor: pal.colors.borderLinkHover}}>
+        hoverStyle={{borderColor: pal.colors.borderLinkHover}}
+        onLongPress={onShareExternal}>
         <ExternalLinkEmbed link={link} />
       </Link>
     )