about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/alf/typography.tsx91
-rw-r--r--src/components/RichText.tsx15
-rw-r--r--src/components/Typography.tsx8
-rw-r--r--src/components/dms/MessageItem.tsx3
-rw-r--r--src/view/com/util/text/Text.tsx23
5 files changed, 51 insertions, 89 deletions
diff --git a/src/alf/typography.tsx b/src/alf/typography.tsx
index d00910bf8..1b1e81bd4 100644
--- a/src/alf/typography.tsx
+++ b/src/alf/typography.tsx
@@ -1,10 +1,11 @@
-import React from 'react'
+import React, {Children} from 'react'
 import {TextProps as RNTextProps} from 'react-native'
 import {StyleProp, TextStyle} from 'react-native'
 import {UITextView} from 'react-native-uitextview'
 import createEmojiRegex from 'emoji-regex'
 
 import {isNative} from '#/platform/detection'
+import {isIOS} from '#/platform/detection'
 import {Alf, applyFonts, atoms, flatten} from '#/alf'
 
 /**
@@ -57,7 +58,7 @@ export function normalizeTextStyles(
 }
 
 export type StringChild = string | (string | null)[]
-export type TextProps = Omit<RNTextProps, 'children'> & {
+export type TextProps = RNTextProps & {
   /**
    * Lets the user select text, to use the native copy and paste functionality.
    */
@@ -71,65 +72,55 @@ export type TextProps = Omit<RNTextProps, 'children'> & {
    * Appears as a small tooltip on web hover.
    */
   title?: string
-} & (
-    | {
-        emoji?: true
-        children: StringChild
-      }
-    | {
-        emoji?: false
-        children: RNTextProps['children']
-      }
-  )
+  /**
+   * Whether the children could possibly contain emoji.
+   */
+  emoji?: boolean
+}
 
 const EMOJI = createEmojiRegex()
 
 export function childHasEmoji(children: React.ReactNode) {
-  return (Array.isArray(children) ? children : [children]).some(
-    child => typeof child === 'string' && createEmojiRegex().test(child),
-  )
-}
-
-export function childIsString(
-  children: React.ReactNode,
-): children is StringChild {
-  return (
-    typeof children === 'string' ||
-    (Array.isArray(children) &&
-      children.every(child => typeof child === 'string' || child === null))
-  )
+  let hasEmoji = false
+  Children.forEach(children, child => {
+    if (typeof child === 'string' && createEmojiRegex().test(child)) {
+      hasEmoji = true
+    }
+  })
+  return hasEmoji
 }
 
 export function renderChildrenWithEmoji(
-  children: StringChild,
+  children: React.ReactNode,
   props: Omit<TextProps, 'children'> = {},
+  emoji: boolean,
 ) {
-  const normalized = Array.isArray(children) ? children : [children]
+  if (!isIOS || !emoji) {
+    return children
+  }
+  return Children.map(children, child => {
+    if (typeof child !== 'string') return child
 
-  return (
-    <UITextView {...props}>
-      {normalized.map(child => {
-        if (typeof child !== 'string') return child
+    const emojis = child.match(EMOJI)
 
-        const emojis = child.match(EMOJI)
+    if (emojis === null) {
+      return child
+    }
 
-        if (emojis === null) {
-          return child
-        }
+    return child.split(EMOJI).map((stringPart, index) => [
+      stringPart,
+      emojis[index] ? (
+        <UITextView
+          {...props}
+          style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
+          {emojis[index]}
+        </UITextView>
+      ) : null,
+    ])
+  })
+}
 
-        return child.split(EMOJI).map((stringPart, index) => (
-          <UITextView key={index} {...props}>
-            {stringPart}
-            {emojis[index] ? (
-              <UITextView
-                {...props}
-                style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
-                {emojis[index]}
-              </UITextView>
-            ) : null}
-          </UITextView>
-        ))
-      })}
-    </UITextView>
-  )
+const SINGLE_EMOJI_RE = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u
+export function isOnlyEmoji(text: string) {
+  return text.length <= 15 && SINGLE_EMOJI_RE.test(text)
 }
diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx
index e2d05ac6c..6d7e50e48 100644
--- a/src/components/RichText.tsx
+++ b/src/components/RichText.tsx
@@ -9,6 +9,7 @@ import {NavigationProp} from '#/lib/routes/types'
 import {toShortUrl} from '#/lib/strings/url-helpers'
 import {isNative} from '#/platform/detection'
 import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf'
+import {isOnlyEmoji} from '#/alf/typography'
 import {useInteractionState} from '#/components/hooks/useInteractionState'
 import {InlineLinkText, LinkProps} from '#/components/Link'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
@@ -150,17 +151,14 @@ export function RichText({
         />,
       )
     } else {
-      els.push(
-        <Text key={key} emoji style={plainStyles}>
-          {segment.text}
-        </Text>,
-      )
+      els.push(segment.text)
     }
     key++
   }
 
   return (
     <Text
+      emoji
       selectable={selectable}
       testID={testID}
       style={plainStyles}
@@ -250,10 +248,3 @@ function RichTextTag({
     </React.Fragment>
   )
 }
-
-export function isOnlyEmoji(text: string) {
-  return (
-    text.length <= 15 &&
-    /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u.test(text)
-  )
-}
diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx
index 4bcbf9f09..3e202cb8f 100644
--- a/src/components/Typography.tsx
+++ b/src/components/Typography.tsx
@@ -1,11 +1,9 @@
 import {UITextView} from 'react-native-uitextview'
 
 import {logger} from '#/logger'
-import {isIOS} from '#/platform/detection'
 import {atoms, flatten, useAlf, useTheme, web} from '#/alf'
 import {
   childHasEmoji,
-  childIsString,
   normalizeTextStyles,
   renderChildrenWithEmoji,
   TextProps,
@@ -39,10 +37,6 @@ export function Text({
         `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
       )
     }
-
-    if (emoji && !childIsString(children)) {
-      logger.error('Text: when <Text emoji />, children can only be strings.')
-    }
   }
 
   const shared = {
@@ -55,7 +49,7 @@ export function Text({
 
   return (
     <UITextView {...shared}>
-      {isIOS && emoji ? renderChildrenWithEmoji(children, shared) : children}
+      {renderChildrenWithEmoji(children, shared, emoji ?? false)}
     </UITextView>
   )
 }
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx
index 52220e2ca..79f0997fd 100644
--- a/src/components/dms/MessageItem.tsx
+++ b/src/components/dms/MessageItem.tsx
@@ -19,10 +19,11 @@ import {ConvoItem} from '#/state/messages/convo/types'
 import {useSession} from '#/state/session'
 import {TimeElapsed} from '#/view/com/util/TimeElapsed'
 import {atoms as a, useTheme} from '#/alf'
+import {isOnlyEmoji} from '#/alf/typography'
 import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
 import {InlineLinkText} from '#/components/Link'
 import {Text} from '#/components/Typography'
-import {isOnlyEmoji, RichText} from '../RichText'
+import {RichText} from '../RichText'
 import {DateDivider} from './DateDivider'
 import {MessageItemEmbed} from './MessageItemEmbed'
 import {localDateString} from './util'
diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx
index a5278e0a0..f05274f44 100644
--- a/src/view/com/util/text/Text.tsx
+++ b/src/view/com/util/text/Text.tsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {StyleSheet, Text as RNText, TextProps} from 'react-native'
+import {StyleSheet, TextProps} from 'react-native'
 import {UITextView} from 'react-native-uitextview'
 
 import {lh, s} from '#/lib/styles'
@@ -9,7 +9,6 @@ import {isIOS, isWeb} from '#/platform/detection'
 import {applyFonts, useAlf} from '#/alf'
 import {
   childHasEmoji,
-  childIsString,
   renderChildrenWithEmoji,
   StringChild,
 } from '#/alf/typography'
@@ -56,10 +55,6 @@ function Text_DEPRECATED({
         `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
       )
     }
-
-    if (emoji && !childIsString(children)) {
-      logger.error('Text: when <Text emoji />, children can only be strings.')
-    }
   }
 
   const textProps = React.useMemo(() => {
@@ -107,19 +102,9 @@ function Text_DEPRECATED({
     type,
   ])
 
-  if (selectable && isIOS) {
-    return (
-      <UITextView {...textProps}>
-        {isIOS && emoji
-          ? renderChildrenWithEmoji(children, textProps)
-          : children}
-      </UITextView>
-    )
-  }
-
   return (
-    <RNText {...textProps}>
-      {isIOS && emoji ? renderChildrenWithEmoji(children, textProps) : children}
-    </RNText>
+    <UITextView {...textProps}>
+      {renderChildrenWithEmoji(children, textProps, emoji ?? false)}
+    </UITextView>
   )
 }