about summary refs log tree commit diff
path: root/src/screens/Messages/components/MessageInputEmbed.tsx
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2024-10-02 22:21:59 +0300
committerGitHub <noreply@github.com>2024-10-02 22:21:59 +0300
commit13c9c79aeec77edc33b1a926843b005c14acccc7 (patch)
tree0ce80c052a7e504c99e842f0ba6a0f1f2f379a1e /src/screens/Messages/components/MessageInputEmbed.tsx
parent405966830ccdbee6152037eebb76c4815ff5526c (diff)
downloadvoidsky-13c9c79aeec77edc33b1a926843b005c14acccc7.tar.zst
move files around (#5576)
Diffstat (limited to 'src/screens/Messages/components/MessageInputEmbed.tsx')
-rw-r--r--src/screens/Messages/components/MessageInputEmbed.tsx219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx
new file mode 100644
index 000000000..2d1551019
--- /dev/null
+++ b/src/screens/Messages/components/MessageInputEmbed.tsx
@@ -0,0 +1,219 @@
+import React, {useCallback, useEffect, useMemo, useState} from 'react'
+import {LayoutAnimation, View} from 'react-native'
+import {
+  AppBskyFeedPost,
+  AppBskyRichtextFacet,
+  AtUri,
+  RichText as RichTextAPI,
+} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'
+
+import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
+import {makeProfileLink} from '#/lib/routes/links'
+import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
+import {
+  convertBskyAppUrlIfNeeded,
+  isBskyPostUrl,
+  makeRecordUri,
+} from '#/lib/strings/url-helpers'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {usePostQuery} from '#/state/queries/post'
+import {PostMeta} from '#/view/com/util/PostMeta'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonIcon} from '#/components/Button'
+import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
+import {Loader} from '#/components/Loader'
+import * as MediaPreview from '#/components/MediaPreview'
+import {ContentHider} from '#/components/moderation/ContentHider'
+import {PostAlerts} from '#/components/moderation/PostAlerts'
+import {RichText} from '#/components/RichText'
+import {Text} from '#/components/Typography'
+
+export function useMessageEmbed() {
+  const route =
+    useRoute<RouteProp<CommonNavigatorParams, 'MessagesConversation'>>()
+  const navigation = useNavigation<NavigationProp>()
+  const embedFromParams = route.params.embed
+
+  const [embedUri, setEmbed] = useState(embedFromParams)
+
+  if (embedFromParams && embedUri !== embedFromParams) {
+    setEmbed(embedFromParams)
+  }
+
+  return {
+    embedUri,
+    setEmbed: useCallback(
+      (embedUrl: string | undefined) => {
+        if (!embedUrl) {
+          navigation.setParams({embed: ''})
+          setEmbed(undefined)
+          return
+        }
+
+        if (embedFromParams) return
+
+        const url = convertBskyAppUrlIfNeeded(embedUrl)
+        const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
+        const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
+
+        setEmbed(uri)
+      },
+      [embedFromParams, navigation],
+    ),
+  }
+}
+
+export function useExtractEmbedFromFacets(
+  message: string,
+  setEmbed: (embedUrl: string | undefined) => void,
+) {
+  const rt = new RichTextAPI({text: message})
+  rt.detectFacetsWithoutResolution()
+
+  let uriFromFacet: string | undefined
+
+  for (const facet of rt.facets ?? []) {
+    for (const feature of facet.features) {
+      if (AppBskyRichtextFacet.isLink(feature) && isBskyPostUrl(feature.uri)) {
+        uriFromFacet = feature.uri
+        break
+      }
+    }
+  }
+
+  useEffect(() => {
+    if (uriFromFacet) {
+      setEmbed(uriFromFacet)
+    }
+  }, [uriFromFacet, setEmbed])
+}
+
+export function MessageInputEmbed({
+  embedUri,
+  setEmbed,
+}: {
+  embedUri: string | undefined
+  setEmbed: (embedUrl: string | undefined) => void
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+
+  const {data: post, status} = usePostQuery(embedUri)
+
+  const moderationOpts = useModerationOpts()
+  const moderation = useMemo(
+    () =>
+      moderationOpts && post ? moderatePost(post, moderationOpts) : undefined,
+    [moderationOpts, post],
+  )
+
+  const {rt, record} = useMemo(() => {
+    if (
+      post &&
+      AppBskyFeedPost.isRecord(post.record) &&
+      AppBskyFeedPost.validateRecord(post.record).success
+    ) {
+      return {
+        rt: new RichTextAPI({
+          text: post.record.text,
+          facets: post.record.facets,
+        }),
+        record: post.record,
+      }
+    }
+
+    return {rt: undefined, record: undefined}
+  }, [post])
+
+  if (!embedUri) {
+    return null
+  }
+
+  let content = null
+  switch (status) {
+    case 'pending':
+      content = (
+        <View
+          style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}>
+          <Loader />
+        </View>
+      )
+      break
+    case 'error':
+      content = (
+        <View
+          style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}>
+          <Text style={a.text_center}>Could not fetch post</Text>
+        </View>
+      )
+      break
+    case 'success':
+      const itemUrip = new AtUri(post.uri)
+      const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey)
+
+      if (!post || !moderation || !rt || !record) {
+        return null
+      }
+
+      content = (
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg,
+            t.atoms.border_contrast_low,
+            a.rounded_md,
+            a.border,
+            a.p_sm,
+            a.mb_sm,
+          ]}
+          pointerEvents="none">
+          <PostMeta
+            showAvatar
+            author={post.author}
+            moderation={moderation}
+            timestamp={post.indexedAt}
+            postHref={itemHref}
+            style={a.flex_0}
+          />
+          <ContentHider modui={moderation.ui('contentView')}>
+            <PostAlerts modui={moderation.ui('contentView')} style={a.py_xs} />
+            {rt.text && (
+              <View style={a.mt_xs}>
+                <RichText
+                  enableTags
+                  testID="postText"
+                  value={rt}
+                  style={[a.text_sm, t.atoms.text_contrast_high]}
+                  authorHandle={post.author.handle}
+                  numberOfLines={3}
+                />
+              </View>
+            )}
+            <MediaPreview.Embed embed={post.embed} style={a.mt_sm} />
+          </ContentHider>
+        </View>
+      )
+      break
+  }
+
+  return (
+    <View style={[a.flex_row, a.gap_sm]}>
+      {content}
+      <Button
+        label={_(msg`Remove embed`)}
+        onPress={() => {
+          LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
+          setEmbed(undefined)
+        }}
+        size="tiny"
+        variant="solid"
+        color="secondary"
+        shape="round">
+        <ButtonIcon icon={X} />
+      </Button>
+    </View>
+  )
+}