about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2024-06-03 20:07:01 -0500
committerGitHub <noreply@github.com>2024-06-04 02:07:01 +0100
commitde93e8de746f3c8a7b1755aaa034043951371ae0 (patch)
tree71ede1691ff2f6c8bd5fa9b55e4d708ad876cdd3
parentda96fb1ef5a37018b6a238c3614e9b845d8e2686 (diff)
downloadvoidsky-de93e8de746f3c8a7b1755aaa034043951371ae0.tar.zst
[🐴] Post embeds polish (#4339)
* Handle message cleanup

* Handle last message in chat list

* Memoize lastMessage
-rw-r--r--src/lib/strings/url-helpers.ts21
-rw-r--r--src/screens/Messages/Conversation/MessagesList.tsx26
-rw-r--r--src/screens/Messages/List/ChatListItem.tsx77
3 files changed, 94 insertions, 30 deletions
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index 2a20373a4..4c75f47ad 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -3,6 +3,7 @@ import psl from 'psl'
 import TLDs from 'tlds'
 
 import {BSKY_SERVICE} from 'lib/constants'
+import {isInvalidHandle} from 'lib/strings/handles'
 
 export const BSKY_APP_HOST = 'https://bsky.app'
 const BSKY_TRUSTED_HOSTS = [
@@ -83,6 +84,10 @@ export function toShareUrl(url: string): string {
   return url
 }
 
+export function toBskyAppUrl(url: string): string {
+  return new URL(url, BSKY_APP_HOST).toString()
+}
+
 export function isBskyAppUrl(url: string): boolean {
   return url.startsWith('https://bsky.app/')
 }
@@ -183,6 +188,22 @@ export function feedUriToHref(url: string): string {
   }
 }
 
+export function postUriToRelativePath(
+  uri: string,
+  options?: {handle?: string},
+): string | undefined {
+  try {
+    const {hostname, rkey} = new AtUri(uri)
+    const handleOrDid =
+      options?.handle && !isInvalidHandle(options.handle)
+        ? options.handle
+        : hostname
+    return `/profile/${handleOrDid}/post/${rkey}`
+  } catch {
+    return undefined
+  }
+}
+
 /**
  * Checks if the label in the post text matches the host of the link facet.
  *
diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx
index e6f657b49..f72515ac6 100644
--- a/src/screens/Messages/Conversation/MessagesList.tsx
+++ b/src/screens/Messages/Conversation/MessagesList.tsx
@@ -312,25 +312,19 @@ export function MessagesList({
             })
 
             if (postLinkFacet) {
+              const isAtStart = postLinkFacet.index.byteStart === 0
+              const isAtEnd =
+                postLinkFacet.index.byteEnd === rt.unicodeText.graphemeLength
+
               // remove the post link from the text
-              rt.delete(
-                postLinkFacet.index.byteStart,
-                postLinkFacet.index.byteEnd,
-              )
-
-              // re-trim the text, now that we've removed the post link
-              //
-              // if the post link is at the start of the text, we don't want to leave a leading space
-              // so trim on both sides
-              if (postLinkFacet.index.byteStart === 0) {
-                rt = new RichText({text: rt.text.trim()}, {cleanNewlines: true})
-              } else {
-                // otherwise just trim the end
-                rt = new RichText(
-                  {text: rt.text.trimEnd()},
-                  {cleanNewlines: true},
+              if (isAtStart || isAtEnd) {
+                rt.delete(
+                  postLinkFacet.index.byteStart,
+                  postLinkFacet.index.byteEnd,
                 )
               }
+
+              rt = new RichText({text: rt.text.trim()}, {cleanNewlines: true})
             }
           }
         } catch (error) {
diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx
index d5658249d..9f8808366 100644
--- a/src/screens/Messages/List/ChatListItem.tsx
+++ b/src/screens/Messages/List/ChatListItem.tsx
@@ -2,6 +2,7 @@ import React, {useCallback, useState} from 'react'
 import {GestureResponderEvent, View} from 'react-native'
 import {
   AppBskyActorDefs,
+  AppBskyEmbedRecord,
   ChatBskyConvoDefs,
   moderateProfile,
   ModerationOpts,
@@ -9,6 +10,11 @@ import {
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {
+  postUriToRelativePath,
+  toBskyAppUrl,
+  toShortUrl,
+} from '#/lib/strings/url-helpers'
 import {isNative} from '#/platform/detection'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -95,21 +101,64 @@ function ChatListItemReady({
 
   const isDimStyle = convo.muted || moderation.blocked || isDeletedAccount
 
-  let lastMessage = _(msg`No messages yet`)
-  let lastMessageSentAt: string | null = null
-  if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) {
-    if (convo.lastMessage.sender?.did === currentAccount?.did) {
-      lastMessage = _(msg`You: ${convo.lastMessage.text}`)
-    } else {
-      lastMessage = convo.lastMessage.text
+  const {lastMessage, lastMessageSentAt} = React.useMemo(() => {
+    let lastMessage = _(msg`No messages yet`)
+    let lastMessageSentAt: string | null = null
+
+    if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) {
+      const isFromMe = convo.lastMessage.sender?.did === currentAccount?.did
+
+      if (convo.lastMessage.text) {
+        if (isFromMe) {
+          lastMessage = _(msg`You: ${convo.lastMessage.text}`)
+        } else {
+          lastMessage = convo.lastMessage.text
+        }
+      } else if (convo.lastMessage.embed) {
+        const defaultEmbeddedContentMessage = _(
+          msg`(contains embedded content)`,
+        )
+
+        if (AppBskyEmbedRecord.isView(convo.lastMessage.embed)) {
+          const embed = convo.lastMessage.embed
+
+          if (AppBskyEmbedRecord.isViewRecord(embed.record)) {
+            const record = embed.record
+            const path = postUriToRelativePath(record.uri, {
+              handle: record.author.handle,
+            })
+            const href = path ? toBskyAppUrl(path) : undefined
+            const short = href
+              ? toShortUrl(href)
+              : defaultEmbeddedContentMessage
+            if (isFromMe) {
+              lastMessage = _(msg`You: ${short}`)
+            } else {
+              lastMessage = short
+            }
+          }
+        } else {
+          if (isFromMe) {
+            lastMessage = _(msg`You: ${defaultEmbeddedContentMessage}`)
+          } else {
+            lastMessage = defaultEmbeddedContentMessage
+          }
+        }
+      }
+
+      lastMessageSentAt = convo.lastMessage.sentAt
     }
-    lastMessageSentAt = convo.lastMessage.sentAt
-  }
-  if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) {
-    lastMessage = isDeletedAccount
-      ? _(msg`Conversation deleted`)
-      : _(msg`Message deleted`)
-  }
+    if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) {
+      lastMessage = isDeletedAccount
+        ? _(msg`Conversation deleted`)
+        : _(msg`Message deleted`)
+    }
+
+    return {
+      lastMessage,
+      lastMessageSentAt,
+    }
+  }, [_, convo.lastMessage, currentAccount?.did, isDeletedAccount])
 
   const [showActions, setShowActions] = useState(false)