From 5216464458f4ffd1d6384a1d15ca7be5e8a96d5d Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 12 Jan 2024 22:50:15 -0800 Subject: [PATCH] properly handle link press events --- .../ios/ExpoSelectableTextModule.swift | 6 + .../src/ExpoSelectableTextView.tsx | 132 ++++++++++++------ src/view/com/util/text/RichText.tsx | 3 +- src/view/com/util/text/Text.tsx | 2 - 4 files changed, 97 insertions(+), 46 deletions(-) diff --git a/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift b/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift index 5461041038..13fafb6e88 100644 --- a/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift +++ b/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift @@ -10,7 +10,9 @@ public class ExpoSelectableTextModule: Module { Prop("segments") { (view: ExpoSelectableTextView, prop: String) in // Convert the JSON to segments if let data = prop.data(using: .utf8) { + print("first") if let segments = try? JSONDecoder().decode(TextSegments.self, from: data) { + print("second") view.segments = segments.segments } } @@ -27,6 +29,10 @@ public class ExpoSelectableTextModule: Module { Prop("selectable") { (view: ExpoSelectableTextView, prop: Bool) in view.textView.isSelectable = prop } + + Prop("children") { (view: ExpoSelectableTextView, prop: JavaScriptValue) in + print(prop) + } } } } diff --git a/modules/expo-selectable-text/src/ExpoSelectableTextView.tsx b/modules/expo-selectable-text/src/ExpoSelectableTextView.tsx index 024e3bc3ac..c2df72c98d 100644 --- a/modules/expo-selectable-text/src/ExpoSelectableTextView.tsx +++ b/modules/expo-selectable-text/src/ExpoSelectableTextView.tsx @@ -8,7 +8,11 @@ import { ExpoProTextSegment, ExpoProTextViewProps, } from './ExpoSelectableText.types' -import {Text, View} from 'react-native' +import {StyleSheet, View} from 'react-native' +import {onTextLinkPress, TextLink} from 'view/com/util/Link' +import {useNavigation} from '@react-navigation/native' +import {NavigationProp} from 'lib/routes/types' +import {useModalControls} from 'state/modals' const NativeView: React.ComponentType = requireNativeViewManager('ExpoSelectableText') @@ -16,11 +20,18 @@ const NativeView: React.ComponentType = export default function ExpoSelectableTextView({ style, children, - selectable = false, + selectable = true, onPress, onLongPress, }: ExpoProTextViewProps) { + // Dimensions based on the native view's text height const [dims, setDims] = React.useState({height: 0}) + + // Needed for navigation on link presses + const navigation = useNavigation() + const {openModal, closeModal} = useModalControls() + + // Store the callbacks for onPress and onLongPress events const segmentPressCallbacks = React.useRef< Array<{index: number; onPress: () => void}> >([]) @@ -28,73 +39,108 @@ export default function ExpoSelectableTextView({ Array<{index: number; onLongPress: () => void}> >([]) - const onTextLayout = React.useCallback((e: ExpoProTextLayoutEvent) => { - console.log('layout') - setDims({ - height: e.nativeEvent.height, - }) - }, []) - - const onTextPress = React.useCallback((e: ExpoProTextPressEvent) => { - const onPressSegment = segmentPressCallbacks.current.find( - s => s.index === e.nativeEvent.index, - ) - onPressSegment?.onPress() - }, []) + // The root style, stringified + const rootStyle = React.useMemo(() => { + return style ? JSON.stringify(style) : undefined + }, [style]) + // The text segments, stringified const textSegments = React.useMemo(() => { const segments: ExpoProTextSegment[] = [] for (const [index, child] of React.Children.toArray(children).entries()) { + // Most of our children will be strings. Simply add them to the segments array. if (typeof child === 'string') { segments.push({ index, text: child, - style, + style: style, handlePress: onPress !== undefined, handleLongPress: onLongPress !== undefined, }) - } else if ( - React.isValidElement(child) && - (child as React.ReactElement).type === Text - ) { - const {onPress, onLongPress, children, style} = child.props + } else if (React.isValidElement(child)) { + // If it is a child, it is either a nested or a . Check if the child is a string or a + // If it's a we need to create on the onPress handler (it won't be created in the component since the + // component never actually gets rendered) - segments.push({ - index, - text: children, - style, - handlePress: onPress !== undefined, - handleLongPress: onLongPress !== undefined, - }) + const { + children: innerChildren, + onLongPress: innerOnLongPress, + style: innerStyle, + text: innerText, + href, + navigationAction, + warnOnMismatchingLabel, + } = child.props + let innerOnPress = child.props.onPress - if (onPress !== undefined) { - segmentPressCallbacks.current.push({ + const type = (child as React.ReactElement).type + + if (typeof innerChildren === 'string' || type === TextLink) { + if (type === TextLink) { + // Set the onPress handler + innerOnPress = () => { + onTextLinkPress({ + openModal, + closeModal, + text: innerText, + navigation, + href, + navigationAction, + warnOnMismatchingLabel, + }) + } + } + + // Add the segment to the array + segments.push({ index, - onPress, + text: innerText ?? innerChildren, + style: StyleSheet.flatten(innerStyle), + handlePress: innerOnPress !== undefined, + handleLongPress: innerOnLongPress !== undefined, }) - } - if (onLongPress !== undefined) { - segmentLongPressCallbacks.current.push({ - index, - onLongPress, - }) + // If we have press events, push them in + if (innerOnPress !== undefined) { + segmentPressCallbacks.current.push({ + index, + onPress: innerOnPress, + }) + } + if (onLongPress !== undefined) { + segmentLongPressCallbacks.current.push({ + index, + onLongPress: innerOnLongPress, + }) + } } } } return segments - }, [children, onLongPress, onPress, style]) + }, [children, closeModal, navigation, onLongPress, onPress, openModal, style]) const segmentsJson = React.useMemo(() => { - const json = JSON.stringify({segments: textSegments}) - return json + return JSON.stringify({segments: textSegments}) }, [textSegments]) - const rootStyle = React.useMemo(() => { - return style ? JSON.stringify(style) : undefined - }, [style]) + const onTextLayout = React.useCallback((e: ExpoProTextLayoutEvent) => { + setDims({ + height: e.nativeEvent.height, + }) + }, []) + + const onTextPress = React.useCallback( + (e: ExpoProTextPressEvent) => { + const onPressSegment = + segmentPressCallbacks.current.find(s => s.index === e.nativeEvent.index) + ?.onPress ?? onPress + + onPressSegment?.() + }, + [onPress], + ) return ( diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx index 6a784a2860..0a8d479936 100644 --- a/src/view/com/util/text/RichText.tsx +++ b/src/view/com/util/text/RichText.tsx @@ -108,7 +108,8 @@ export function RichText({ style={[style, pal.text, lineHeightStyle]} numberOfLines={numberOfLines} // @ts-ignore web only -prf - dataSet={WORD_WRAP}> + dataSet={WORD_WRAP} + selectable={selectable}> {els} ) diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx index 04e0e2a5ae..91b960c57f 100644 --- a/src/view/com/util/text/Text.tsx +++ b/src/view/com/util/text/Text.tsx @@ -27,8 +27,6 @@ export function Text({ const typography = theme.typography[type] const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined - // {"color":"#000000","fontSize":20,"letterSpacing":0.2,"fontWeight":"400","flex":1,"lineHeight":26} - // if (false) { // TODO remove if (selectable && isIOS) {