diff options
Diffstat (limited to 'src/view/com/composer')
-rw-r--r-- | src/view/com/composer/Autocomplete.tsx | 4 | ||||
-rw-r--r-- | src/view/com/composer/ComposePost.tsx | 141 | ||||
-rw-r--r-- | src/view/com/composer/ExternalEmbed.tsx | 42 | ||||
-rw-r--r-- | src/view/com/composer/Prompt.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/SelectedPhoto.tsx | 5 | ||||
-rw-r--r-- | src/view/com/composer/char-progress/CharProgress.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/char-progress/CharProgress.web.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/photos/PhotoCarouselPicker.tsx | 112 | ||||
-rw-r--r-- | src/view/com/composer/photos/PhotoCarouselPicker.web.tsx | 6 | ||||
-rw-r--r-- | src/view/com/composer/text-input/TextInput.tsx | 14 | ||||
-rw-r--r-- | src/view/com/composer/text-input/TextInput.web.tsx | 11 |
11 files changed, 194 insertions, 147 deletions
diff --git a/src/view/com/composer/Autocomplete.tsx b/src/view/com/composer/Autocomplete.tsx index 94381e644..c17e41b37 100644 --- a/src/view/com/composer/Autocomplete.tsx +++ b/src/view/com/composer/Autocomplete.tsx @@ -5,9 +5,9 @@ import { StyleSheet, useWindowDimensions, } from 'react-native' -import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' +import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' +import {usePalette} from 'lib/hooks/usePalette' import {Text} from '../util/text/Text' -import {usePalette} from '../../lib/hooks/usePalette' interface AutocompleteItem { handle: string diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index 1144b5e48..6431a11aa 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -3,10 +3,12 @@ import {observer} from 'mobx-react-lite' import { ActivityIndicator, KeyboardAvoidingView, + NativeSyntheticEvent, Platform, SafeAreaView, ScrollView, StyleSheet, + TextInputSelectionChangeEventData, TouchableOpacity, TouchableWithoutFeedback, View, @@ -16,8 +18,9 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -// import {useAnalytics} from '@segment/analytics-react-native' TODO -import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view' +import {useAnalytics} from 'lib/analytics' +import _isEqual from 'lodash.isequal' +import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view' import {Autocomplete} from './Autocomplete' import {ExternalEmbed} from './ExternalEmbed' import {Text} from '../util/text/Text' @@ -26,24 +29,27 @@ import {TextInput, TextInputRef} from './text-input/TextInput' import {CharProgress} from './char-progress/CharProgress' import {TextLink} from '../util/Link' import {UserAvatar} from '../util/UserAvatar' -import {useStores} from '../../../state' -import * as apilib from '../../../state/lib/api' -import {ComposerOpts} from '../../../state/models/shell-ui' -import {s, colors, gradients} from '../../lib/styles' -import { - detectLinkables, - extractEntities, - cleanError, -} from '../../../lib/strings' -import {getLinkMeta} from '../../../lib/link-meta' -import {downloadAndResize} from '../../../lib/images' +import {useStores} from 'state/index' +import * as apilib from 'lib/api/index' +import {ComposerOpts} from 'state/models/shell-ui' +import {s, colors, gradients} from 'lib/styles' +import {cleanError} from 'lib/strings/errors' +import {detectLinkables, extractEntities} from 'lib/strings/rich-text-detection' +import {getLinkMeta} from 'lib/link-meta/link-meta' +import {downloadAndResize} from 'lib/images' import {PhotoCarouselPicker, cropPhoto} from './photos/PhotoCarouselPicker' +import {getMentionAt, insertMentionAt} from 'lib/strings/mention-manip' import {SelectedPhoto} from './SelectedPhoto' -import {usePalette} from '../../lib/hooks/usePalette' +import {usePalette} from 'lib/hooks/usePalette' const MAX_TEXT_LENGTH = 256 const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} +interface Selection { + start: number + end: number +} + export const ComposePost = observer(function ComposePost({ replyTo, imagesOpen, @@ -55,10 +61,11 @@ export const ComposePost = observer(function ComposePost({ onPost?: ComposerOpts['onPost'] onClose: () => void }) { - // const {track} = useAnalytics() TODO + const {track, screen} = useAnalytics() const pal = usePalette('default') const store = useStores() const textInput = useRef<TextInputRef>(null) + const textInputSelection = useRef<Selection>({start: 0, end: 0}) const [isProcessing, setIsProcessing] = useState(false) const [processingState, setProcessingState] = useState('') const [error, setError] = useState('') @@ -66,7 +73,9 @@ export const ComposePost = observer(function ComposePost({ const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>( undefined, ) - const [attemptedExtLinks, setAttemptedExtLinks] = useState<string[]>([]) + const [suggestedExtLinks, setSuggestedExtLinks] = useState<Set<string>>( + new Set(), + ) const [isSelectingPhotos, setIsSelectingPhotos] = useState( imagesOpen || false, ) @@ -117,10 +126,10 @@ export const ComposePost = observer(function ComposePost({ if (extLink.isLoading && extLink.meta?.image && !extLink.localThumb) { downloadAndResize({ uri: extLink.meta.image, - width: 250, - height: 250, + width: 2000, + height: 2000, mode: 'contain', - maxSize: 100000, + maxSize: 1000000, timeout: 15e3, }) .catch(() => undefined) @@ -166,6 +175,7 @@ export const ComposePost = observer(function ComposePost({ textInput.current?.focus() } const onPressSelectPhotos = () => { + track('ComposePost:SelectPhotos') if (isSelectingPhotos) { setIsSelectingPhotos(false) } else if (selectedPhotos.length < 4) { @@ -173,35 +183,31 @@ export const ComposePost = observer(function ComposePost({ } } const onSelectPhotos = (photos: string[]) => { + track('ComposePost:SelectPhotos:Done') setSelectedPhotos(photos) if (photos.length >= 4) { setIsSelectingPhotos(false) } } + const onPressAddLinkCard = (uri: string) => { + setExtLink({uri, isLoading: true}) + } const onChangeText = (newText: string) => { setText(newText) - const prefix = extractTextAutocompletePrefix(newText) - if (typeof prefix === 'string') { + const prefix = getMentionAt(newText, textInputSelection.current?.start || 0) + if (prefix) { autocompleteView.setActive(true) - autocompleteView.setPrefix(prefix) + autocompleteView.setPrefix(prefix.value) } else { autocompleteView.setActive(false) } - if (!extLink && /\s$/.test(newText)) { - const ents = extractEntities(newText) - const entLink = ents - ?.filter( - ent => ent.type === 'link' && !attemptedExtLinks.includes(ent.value), - ) - .pop() // use last - if (entLink) { - setExtLink({ - uri: entLink.value, - isLoading: true, - }) - setAttemptedExtLinks([...attemptedExtLinks, entLink.value]) + if (!extLink) { + const ents = extractEntities(newText)?.filter(ent => ent.type === 'link') + const set = new Set(ents ? ents.map(e => e.value) : []) + if (!_isEqual(set, suggestedExtLinks)) { + setSuggestedExtLinks(set) } } } @@ -218,6 +224,16 @@ export const ComposePost = observer(function ComposePost({ onSelectPhotos([...selectedPhotos, finalImgPath]) } } + const onSelectionChange = ( + evt: NativeSyntheticEvent<TextInputSelectionChangeEventData>, + ) => { + // NOTE we track the input selection using a ref to avoid excessive renders -prf + textInputSelection.current = evt.nativeEvent.selection + } + const onSelectAutocompleteItem = (item: string) => { + setText(insertMentionAt(text, textInputSelection.current?.start || 0, item)) + autocompleteView.setActive(false) + } const onPressCancel = () => hackfixOnClose() const onPressPublish = async () => { if (isProcessing) { @@ -242,11 +258,15 @@ export const ComposePost = observer(function ComposePost({ autocompleteView.knownHandles, setProcessingState, ) - // TODO - // track('Create Post', { - // imageCount: selectedPhotos.length, - // }) + track('Create Post', { + imageCount: selectedPhotos.length, + }) } catch (e: any) { + setExtLink({ + ...extLink, + isLoading: true, + localThumb: undefined, + } as apilib.ExternalEmbedDraft) setError(cleanError(e.message)) setIsProcessing(false) return @@ -256,10 +276,6 @@ export const ComposePost = observer(function ComposePost({ hackfixOnClose() Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`) } - const onSelectAutocompleteItem = (item: string) => { - setText(replaceTextAutocompletePrefix(text, item)) - autocompleteView.setActive(false) - } const canPost = text.length <= MAX_TEXT_LENGTH @@ -386,6 +402,7 @@ export const ComposePost = observer(function ComposePost({ innerRef={textInput} onChangeText={(str: string) => onChangeText(str)} onPaste={onPaste} + onSelectionChange={onSelectionChange} placeholder={selectTextInputPlaceholder} style={[ pal.text, @@ -406,12 +423,27 @@ export const ComposePost = observer(function ComposePost({ /> )} </ScrollView> - {isSelectingPhotos && selectedPhotos.length < 4 && ( + {isSelectingPhotos && selectedPhotos.length < 4 ? ( <PhotoCarouselPicker selectedPhotos={selectedPhotos} onSelectPhotos={onSelectPhotos} /> - )} + ) : !extLink && + selectedPhotos.length === 0 && + suggestedExtLinks.size > 0 ? ( + <View style={s.mb5}> + {Array.from(suggestedExtLinks).map(url => ( + <TouchableOpacity + key={`suggested-${url}`} + style={[pal.borderDark, styles.addExtLinkBtn]} + onPress={() => onPressAddLinkCard(url)}> + <Text> + Add link card: <Text style={pal.link}>{url}</Text> + </Text> + </TouchableOpacity> + ))} + </View> + ) : null} <View style={[pal.border, styles.bottomBar]}> <TouchableOpacity testID="composerSelectPhotosButton" @@ -442,18 +474,6 @@ export const ComposePost = observer(function ComposePost({ ) }) -const atPrefixRegex = /@([a-z0-9.]*)$/i -function extractTextAutocompletePrefix(text: string) { - const match = atPrefixRegex.exec(text) - if (match) { - return match[1] - } - return undefined -} -function replaceTextAutocompletePrefix(text: string, item: string) { - return text.replace(atPrefixRegex, `@${item} `) -} - const styles = StyleSheet.create({ outer: { flexDirection: 'column', @@ -532,6 +552,13 @@ const styles = StyleSheet.create({ paddingLeft: 13, paddingRight: 8, }, + addExtLinkBtn: { + borderWidth: 1, + borderRadius: 24, + paddingHorizontal: 16, + paddingVertical: 12, + marginBottom: 4, + }, bottomBar: { flexDirection: 'row', paddingVertical: 10, diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx index ed4bfb5ed..23dcaffd5 100644 --- a/src/view/com/composer/ExternalEmbed.tsx +++ b/src/view/com/composer/ExternalEmbed.tsx @@ -7,12 +7,11 @@ import { } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {BlurView} from '../util/BlurView' -import LinearGradient from 'react-native-linear-gradient' import {AutoSizedImage} from '../util/images/AutoSizedImage' import {Text} from '../util/text/Text' -import {s, gradients} from '../../lib/styles' -import {usePalette} from '../../lib/hooks/usePalette' -import {ExternalEmbedDraft} from '../../../state/lib/api' +import {s} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {ExternalEmbedDraft} from 'lib/api/index' export const ExternalEmbed = ({ link, @@ -30,31 +29,12 @@ export const ExternalEmbed = ({ <View style={[styles.outer, pal.view, pal.border]}> {link.isLoading ? ( <View - style={[ - styles.image, - styles.imageFallback, - {backgroundColor: pal.colors.backgroundLight}, - ]}> + style={[styles.image, {backgroundColor: pal.colors.backgroundLight}]}> <ActivityIndicator size="large" style={styles.spinner} /> </View> ) : link.localThumb ? ( - <AutoSizedImage - uri={link.localThumb.path} - containerStyle={styles.image} - /> - ) : ( - <LinearGradient - colors={[gradients.blueDark.start, gradients.blueDark.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.image, styles.imageFallback]} - /> - )} - <TouchableWithoutFeedback onPress={onRemove}> - <BlurView style={styles.removeBtn} blurType="dark"> - <FontAwesomeIcon size={18} icon="xmark" style={s.white} /> - </BlurView> - </TouchableWithoutFeedback> + <AutoSizedImage uri={link.localThumb.path} style={styles.image} /> + ) : undefined} <View style={styles.inner}> {!!link.meta?.title && ( <Text type="sm-bold" numberOfLines={2} style={[pal.text]}> @@ -81,6 +61,11 @@ export const ExternalEmbed = ({ </Text> )} </View> + <TouchableWithoutFeedback onPress={onRemove}> + <BlurView style={styles.removeBtn} blurType="dark"> + <FontAwesomeIcon size={18} icon="xmark" style={s.white} /> + </BlurView> + </TouchableWithoutFeedback> </View> ) } @@ -98,10 +83,7 @@ const styles = StyleSheet.create({ borderTopLeftRadius: 6, borderTopRightRadius: 6, width: '100%', - height: 200, - }, - imageFallback: { - height: 160, + maxHeight: 200, }, removeBtn: { position: 'absolute', diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx index d0c023e25..46a0cec62 100644 --- a/src/view/com/composer/Prompt.tsx +++ b/src/view/com/composer/Prompt.tsx @@ -2,7 +2,7 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../util/text/Text' -import {usePalette} from '../../lib/hooks/usePalette' +import {usePalette} from 'lib/hooks/usePalette' export function ComposePrompt({ text = "What's up?", diff --git a/src/view/com/composer/SelectedPhoto.tsx b/src/view/com/composer/SelectedPhoto.tsx index dd508fe1f..6aeda33cd 100644 --- a/src/view/com/composer/SelectedPhoto.tsx +++ b/src/view/com/composer/SelectedPhoto.tsx @@ -1,7 +1,8 @@ import React, {useCallback} from 'react' -import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' +import {StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {colors} from '../../lib/styles' +import Image from 'view/com/util/images/Image' +import {colors} from 'lib/styles' export const SelectedPhoto = ({ selectedPhotos, diff --git a/src/view/com/composer/char-progress/CharProgress.tsx b/src/view/com/composer/char-progress/CharProgress.tsx index d4093064b..cd7cb2c4e 100644 --- a/src/view/com/composer/char-progress/CharProgress.tsx +++ b/src/view/com/composer/char-progress/CharProgress.tsx @@ -5,7 +5,7 @@ import {Text} from '../../util/text/Text' import ProgressCircle from 'react-native-progress/Circle' // @ts-ignore no type definition -prf import ProgressPie from 'react-native-progress/Pie' -import {s, colors} from '../../../lib/styles' +import {s, colors} from 'lib/styles' const MAX_TEXT_LENGTH = 256 const DANGER_TEXT_LENGTH = MAX_TEXT_LENGTH diff --git a/src/view/com/composer/char-progress/CharProgress.web.tsx b/src/view/com/composer/char-progress/CharProgress.web.tsx index dfb2fad58..d32d7a72c 100644 --- a/src/view/com/composer/char-progress/CharProgress.web.tsx +++ b/src/view/com/composer/char-progress/CharProgress.web.tsx @@ -1,7 +1,7 @@ import React from 'react' import {View} from 'react-native' import {Text} from '../../util/text/Text' -import {s} from '../../../lib/styles' +import {s} from 'lib/styles' const MAX_TEXT_LENGTH = 256 const DANGER_TEXT_LENGTH = MAX_TEXT_LENGTH diff --git a/src/view/com/composer/photos/PhotoCarouselPicker.tsx b/src/view/com/composer/photos/PhotoCarouselPicker.tsx index 7a5c9f65d..406f8b04c 100644 --- a/src/view/com/composer/photos/PhotoCarouselPicker.tsx +++ b/src/view/com/composer/photos/PhotoCarouselPicker.tsx @@ -4,6 +4,7 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {useAnalytics} from 'lib/analytics' import { openPicker, openCamera, @@ -12,18 +13,26 @@ import { import { UserLocalPhotosModel, PhotoIdentifier, -} from '../../../../state/models/user-local-photos' -import {compressIfNeeded, scaleDownDimensions} from '../../../../lib/images' -import {usePalette} from '../../../lib/hooks/usePalette' -import {useStores, RootStoreModel} from '../../../../state' +} from 'state/models/user-local-photos' +import { + compressIfNeeded, + moveToPremanantPath, + scaleDownDimensions, +} from 'lib/images' +import {usePalette} from 'lib/hooks/usePalette' +import {useStores, RootStoreModel} from 'state/index' +import { + requestPhotoAccessIfNeeded, + requestCameraAccessIfNeeded, +} from 'lib/permissions' -const MAX_WIDTH = 1000 -const MAX_HEIGHT = 1000 -const MAX_SIZE = 300000 +const MAX_WIDTH = 2000 +const MAX_HEIGHT = 2000 +const MAX_SIZE = 1000000 const IMAGE_PARAMS = { - width: 1000, - height: 1000, + width: 2000, + height: 2000, freeStyleCropEnabled: true, } @@ -46,8 +55,10 @@ export async function cropPhoto( width, height, }) + const img = await compressIfNeeded(cropperRes, MAX_SIZE) - return img.path + const permanentPath = await moveToPremanantPath(img.path) + return permanentPath } export const PhotoCarouselPicker = ({ @@ -57,24 +68,28 @@ export const PhotoCarouselPicker = ({ selectedPhotos: string[] onSelectPhotos: (v: string[]) => void }) => { + const {track} = useAnalytics() const pal = usePalette('default') const store = useStores() - const [localPhotos, setLocalPhotos] = React.useState< - UserLocalPhotosModel | undefined - >(undefined) + const [isSetup, setIsSetup] = React.useState<boolean>(false) + + const localPhotos = React.useMemo<UserLocalPhotosModel>( + () => new UserLocalPhotosModel(store), + [store], + ) - // initial setup React.useEffect(() => { - const photos = new UserLocalPhotosModel(store) - photos.setup().then(() => { - if (photos.photos) { - setLocalPhotos(photos) - } + // initial setup + localPhotos.setup().then(() => { + setIsSetup(true) }) - }, [store]) + }, [localPhotos]) const handleOpenCamera = useCallback(async () => { try { + if (!(await requestCameraAccessIfNeeded())) { + return + } const cameraRes = await openCamera(store, { mediaType: 'photo', ...IMAGE_PARAMS, @@ -89,6 +104,7 @@ export const PhotoCarouselPicker = ({ const handleSelectPhoto = useCallback( async (item: PhotoIdentifier) => { + track('PhotoCarouselPicker:PhotoSelected') try { const imgPath = await cropPhoto( store, @@ -102,37 +118,41 @@ export const PhotoCarouselPicker = ({ store.log.warn('Error selecting photo', err) } }, - [store, selectedPhotos, onSelectPhotos], + [track, store, onSelectPhotos, selectedPhotos], ) - const handleOpenGallery = useCallback(() => { - openPicker(store, { + const handleOpenGallery = useCallback(async () => { + track('PhotoCarouselPicker:GalleryOpened') + if (!(await requestPhotoAccessIfNeeded())) { + return + } + const items = await openPicker(store, { multiple: true, maxFiles: 4 - selectedPhotos.length, mediaType: 'photo', - }).then(async items => { - const result = [] - - for (const image of items) { - // choose target dimensions based on the original - // this causes the photo cropper to start with the full image "selected" - const {width, height} = scaleDownDimensions( - {width: image.width, height: image.height}, - {width: MAX_WIDTH, height: MAX_HEIGHT}, - ) - const cropperRes = await openCropper(store, { - mediaType: 'photo', - path: image.path, - freeStyleCropEnabled: true, - width, - height, - }) - const finalImg = await compressIfNeeded(cropperRes, MAX_SIZE) - result.push(finalImg.path) - } - onSelectPhotos([...selectedPhotos, ...result]) }) - }, [store, selectedPhotos, onSelectPhotos]) + const result = [] + + for (const image of items) { + // choose target dimensions based on the original + // this causes the photo cropper to start with the full image "selected" + const {width, height} = scaleDownDimensions( + {width: image.width, height: image.height}, + {width: MAX_WIDTH, height: MAX_HEIGHT}, + ) + const cropperRes = await openCropper(store, { + mediaType: 'photo', + path: image.path, + ...IMAGE_PARAMS, + width, + height, + }) + const finalImg = await compressIfNeeded(cropperRes, MAX_SIZE) + const permanentPath = await moveToPremanantPath(finalImg.path) + result.push(permanentPath) + } + onSelectPhotos([...selectedPhotos, ...result]) + }, [track, store, selectedPhotos, onSelectPhotos]) return ( <ScrollView @@ -161,7 +181,7 @@ export const PhotoCarouselPicker = ({ size={24} /> </TouchableOpacity> - {localPhotos != null && + {isSetup && localPhotos.photos.map((item: PhotoIdentifier, index: number) => ( <TouchableOpacity testID="openSelectPhotoButton" diff --git a/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx b/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx index bb2800026..607f8e724 100644 --- a/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx +++ b/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx @@ -9,9 +9,9 @@ import { openCamera, openCropper, } from '../../util/images/image-crop-picker/ImageCropPicker' -import {compressIfNeeded, scaleDownDimensions} from '../../../../lib/images' -import {usePalette} from '../../../lib/hooks/usePalette' -import {useStores, RootStoreModel} from '../../../../state' +import {compressIfNeeded, scaleDownDimensions} from 'lib/images' +import {usePalette} from 'lib/hooks/usePalette' +import {useStores, RootStoreModel} from 'state/index' const MAX_WIDTH = 1000 const MAX_HEIGHT = 1000 diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 3c5dacf80..be6150e11 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -1,10 +1,15 @@ import React from 'react' -import {StyleProp, TextStyle} from 'react-native' +import { + NativeSyntheticEvent, + StyleProp, + TextInputSelectionChangeEventData, + TextStyle, +} from 'react-native' import PasteInput, { PastedFile, PasteInputRef, } from '@mattermost/react-native-paste-input' -import {usePalette} from '../../../lib/hooks/usePalette' +import {usePalette} from 'lib/hooks/usePalette' export type TextInputRef = PasteInputRef @@ -14,6 +19,9 @@ interface TextInputProps { placeholder: string style: StyleProp<TextStyle> onChangeText: (str: string) => void + onSelectionChange?: + | ((e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void) + | undefined onPaste: (err: string | undefined, uris: string[]) => void } @@ -23,6 +31,7 @@ export function TextInput({ placeholder, style, onChangeText, + onSelectionChange, onPaste, children, }: React.PropsWithChildren<TextInputProps>) { @@ -44,6 +53,7 @@ export function TextInput({ multiline scrollEnabled onChangeText={(str: string) => onChangeText(str)} + onSelectionChange={onSelectionChange} onPaste={onPasteInner} placeholder={placeholder} placeholderTextColor={pal.colors.textLight} diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 395f8e5a2..2b610850c 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -1,12 +1,14 @@ import React from 'react' import { + NativeSyntheticEvent, StyleProp, StyleSheet, TextInput as RNTextInput, + TextInputSelectionChangeEventData, TextStyle, } from 'react-native' -import {usePalette} from '../../../lib/hooks/usePalette' -import {addStyle} from '../../../lib/addStyle' +import {usePalette} from 'lib/hooks/usePalette' +import {addStyle} from 'lib/styles' export type TextInputRef = RNTextInput @@ -16,6 +18,9 @@ interface TextInputProps { placeholder: string style: StyleProp<TextStyle> onChangeText: (str: string) => void + onSelectionChange?: + | ((e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void) + | undefined onPaste: (err: string | undefined, uris: string[]) => void } @@ -25,6 +30,7 @@ export function TextInput({ placeholder, style, onChangeText, + onSelectionChange, children, }: React.PropsWithChildren<TextInputProps>) { const pal = usePalette('default') @@ -36,6 +42,7 @@ export function TextInput({ multiline scrollEnabled onChangeText={(str: string) => onChangeText(str)} + onSelectionChange={onSelectionChange} placeholder={placeholder} placeholderTextColor={pal.colors.textLight} style={style}> |