diff options
Diffstat (limited to 'src/view/com/util')
-rw-r--r-- | src/view/com/util/Link.tsx | 25 | ||||
-rw-r--r-- | src/view/com/util/WebAuxClickWrapper.tsx | 30 | ||||
-rw-r--r-- | src/view/com/util/forms/Button.tsx | 12 | ||||
-rw-r--r-- | src/view/com/util/forms/DropdownButton.tsx | 40 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx | 8 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/QuoteEmbed.tsx | 16 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 17 |
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> ) |