about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/alf/atoms.ts18
-rw-r--r--src/components/RichText.tsx30
-rw-r--r--src/components/dms/MessageItem.tsx20
-rw-r--r--src/lib/strings/rich-text-manip.ts1
-rw-r--r--src/screens/Messages/Conversation/MessagesList.tsx26
5 files changed, 75 insertions, 20 deletions
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index 45ab72ca6..3e5ddf049 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -840,4 +840,22 @@ export const atoms = {
   mr_auto: {
     marginRight: 'auto',
   },
+  /*
+   * Pointer events
+   */
+  pointer_events_none: {
+    pointerEvents: 'none',
+  },
+  pointer_events_auto: {
+    pointerEvents: 'auto',
+  },
+  /*
+   * Text decoration
+   */
+  underline: {
+    textDecorationLine: 'underline',
+  },
+  strike_through: {
+    textDecorationLine: 'line-through',
+  },
 } as const
diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx
index 0d49e7130..ed69c199a 100644
--- a/src/components/RichText.tsx
+++ b/src/components/RichText.tsx
@@ -1,4 +1,5 @@
 import React from 'react'
+import {TextStyle} from 'react-native'
 import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -26,6 +27,7 @@ export function RichText({
   enableTags = false,
   authorHandle,
   onLinkPress,
+  interactiveStyle,
 }: TextStyleProp &
   Pick<TextProps, 'selectable'> & {
     value: RichTextAPI | string
@@ -35,13 +37,22 @@ export function RichText({
     enableTags?: boolean
     authorHandle?: string
     onLinkPress?: LinkProps['onPress']
+    interactiveStyle?: TextStyle
   }) {
   const richText = React.useMemo(
     () =>
       value instanceof RichTextAPI ? value : new RichTextAPI({text: value}),
     [value],
   )
-  const styles = [a.leading_snug, flatten(style)]
+
+  const flattenedStyle = flatten(style)
+  const plainStyles = [a.leading_snug, flattenedStyle]
+  const interactiveStyles = [
+    a.leading_snug,
+    a.pointer_events_auto,
+    flatten(interactiveStyle),
+    flattenedStyle,
+  ]
 
   const {text, facets} = richText
 
@@ -67,7 +78,7 @@ export function RichText({
       <Text
         selectable={selectable}
         testID={testID}
-        style={styles}
+        style={plainStyles}
         numberOfLines={numberOfLines}
         // @ts-ignore web only -prf
         dataSet={WORD_WRAP}>
@@ -93,7 +104,7 @@ export function RichText({
           <InlineLinkText
             selectable={selectable}
             to={`/profile/${mention.did}`}
-            style={[...styles, {pointerEvents: 'auto'}]}
+            style={interactiveStyles}
             // @ts-ignore TODO
             dataSet={WORD_WRAP}
             onPress={onLinkPress}>
@@ -110,7 +121,7 @@ export function RichText({
             selectable={selectable}
             key={key}
             to={link.uri}
-            style={[...styles, {pointerEvents: 'auto'}]}
+            style={interactiveStyles}
             // @ts-ignore TODO
             dataSet={WORD_WRAP}
             shareOnLongPress
@@ -130,7 +141,7 @@ export function RichText({
           key={key}
           text={segment.text}
           tag={tag.tag}
-          style={styles}
+          style={interactiveStyles}
           selectable={selectable}
           authorHandle={authorHandle}
         />,
@@ -145,7 +156,7 @@ export function RichText({
     <Text
       selectable={selectable}
       testID={testID}
-      style={styles}
+      style={plainStyles}
       numberOfLines={numberOfLines}
       // @ts-ignore web only -prf
       dataSet={WORD_WRAP}>
@@ -219,19 +230,16 @@ function RichTextTag({
           onFocus={onFocus}
           onBlur={onBlur}
           style={[
-            style,
-            {
-              pointerEvents: 'auto',
-              color: t.palette.primary_500,
-            },
             web({
               cursor: 'pointer',
             }),
+            {color: t.palette.primary_500},
             (hovered || focused || pressed) && {
               ...web({outline: 0}),
               textDecorationLine: 'underline',
               textDecorationColor: t.palette.primary_500,
             },
+            style,
           ]}>
           {text}
         </Text>
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx
index f8f5197ca..e9128c5a0 100644
--- a/src/components/dms/MessageItem.tsx
+++ b/src/components/dms/MessageItem.tsx
@@ -1,5 +1,6 @@
 import React, {useCallback, useMemo, useRef} from 'react'
 import {LayoutAnimation, StyleProp, TextStyle, View} from 'react-native'
+import {RichText as RichTextAPI} from '@atproto/api'
 import {ChatBskyConvoDefs} from '@atproto-labs/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -9,8 +10,9 @@ import {TimeElapsed} from 'view/com/util/TimeElapsed'
 import {atoms as a, useTheme} from '#/alf'
 import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
 import {Text} from '#/components/Typography'
+import {RichText} from '../RichText'
 
-export let MessageItem = ({
+let MessageItem = ({
   item,
   next,
   pending,
@@ -65,6 +67,10 @@ export let MessageItem = ({
   const pendingColor =
     t.name === 'light' ? t.palette.primary_200 : t.palette.primary_800
 
+  const rt = useMemo(() => {
+    return new RichTextAPI({text: item.text, facets: item.facets})
+  }, [item.text, item.facets])
+
   return (
     <View>
       <ActionsWrapper isFromSelf={isFromSelf} message={item}>
@@ -87,15 +93,17 @@ export let MessageItem = ({
               ? {borderBottomRightRadius: isLastInGroup ? 2 : 17}
               : {borderBottomLeftRadius: isLastInGroup ? 2 : 17},
           ]}>
-          <Text
+          <RichText
+            value={rt}
             style={[
               a.text_md,
               a.leading_snug,
               isFromSelf && {color: t.palette.white},
               pending && t.name !== 'light' && {color: t.palette.primary_300},
-            ]}>
-            {item.text}
-          </Text>
+            ]}
+            interactiveStyle={a.underline}
+            enableTags
+          />
         </View>
       </ActionsWrapper>
       <MessageItemMetadata
@@ -106,8 +114,8 @@ export let MessageItem = ({
     </View>
   )
 }
-
 MessageItem = React.memo(MessageItem)
+export {MessageItem}
 
 let MessageItemMetadata = ({
   message,
diff --git a/src/lib/strings/rich-text-manip.ts b/src/lib/strings/rich-text-manip.ts
index d9cd8c071..508e0772e 100644
--- a/src/lib/strings/rich-text-manip.ts
+++ b/src/lib/strings/rich-text-manip.ts
@@ -1,4 +1,5 @@
 import {RichText, UnicodeString} from '@atproto/api'
+
 import {toShortUrl} from './url-helpers'
 
 export function shortenLinks(rt: RichText): RichText {
diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx
index 1b07f8877..0b8ab5249 100644
--- a/src/screens/Messages/Conversation/MessagesList.tsx
+++ b/src/screens/Messages/Conversation/MessagesList.tsx
@@ -7,12 +7,15 @@ import {
 import {runOnJS, useSharedValue} from 'react-native-reanimated'
 import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {AppBskyRichtextFacet, RichText} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {shortenLinks} from '#/lib/strings/rich-text-manip'
 import {isIOS} from '#/platform/detection'
 import {useConvo} from '#/state/messages/convo'
 import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types'
+import {useAgent} from '#/state/session'
 import {ScrollProvider} from 'lib/ScrollContext'
 import {isWeb} from 'platform/detection'
 import {List} from 'view/com/util/List'
@@ -87,6 +90,7 @@ function onScrollToIndexFailed() {
 
 export function MessagesList() {
   const convo = useConvo()
+  const {getAgent} = useAgent()
   const flatListRef = useRef<FlatList>(null)
 
   // We need to keep track of when the scroll offset is at the bottom of the list to know when to scroll as new items
@@ -159,14 +163,30 @@ export function MessagesList() {
   }, [convo, hasInitiallyScrolled])
 
   const onSendMessage = useCallback(
-    (text: string) => {
+    async (text: string) => {
+      let rt = new RichText({text}, {cleanNewlines: true})
+      await rt.detectFacets(getAgent())
+      rt = shortenLinks(rt)
+
+      // filter out any mention facets that didn't map to a user
+      rt.facets = rt.facets?.filter(facet => {
+        const mention = facet.features.find(feature =>
+          AppBskyRichtextFacet.isMention(feature),
+        )
+        if (mention && !mention.did) {
+          return false
+        }
+        return true
+      })
+
       if (convo.status === ConvoStatus.Ready) {
         convo.sendMessage({
-          text,
+          text: rt.text,
+          facets: rt.facets,
         })
       }
     },
-    [convo],
+    [convo, getAgent],
   )
 
   const onScroll = React.useCallback(