about summary refs log tree commit diff
path: root/src/view/com/post-thread/PostThreadItem.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/post-thread/PostThreadItem.tsx')
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx463
1 files changed, 231 insertions, 232 deletions
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 351a46706..a4b7a4a9c 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -1,18 +1,17 @@
-import React, {useMemo} from 'react'
-import {observer} from 'mobx-react-lite'
-import {Linking, StyleSheet, View} from 'react-native'
-import Clipboard from '@react-native-clipboard/clipboard'
-import {AtUri, AppBskyFeedDefs} from '@atproto/api'
+import React, {memo, useMemo} from 'react'
+import {StyleSheet, View} from 'react-native'
 import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {PostThreadItemModel} from 'state/models/content/post-thread-item'
+  AtUri,
+  AppBskyFeedDefs,
+  AppBskyFeedPost,
+  RichText as RichTextAPI,
+  moderatePost,
+  PostModeration,
+} from '@atproto/api'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {Link, TextLink} from '../util/Link'
 import {RichText} from '../util/text/RichText'
 import {Text} from '../util/text/Text'
-import {PostDropdownBtn} from '../util/forms/PostDropdownBtn'
-import * as Toast from '../util/Toast'
 import {PreviewableUserAvatar} from '../util/UserAvatar'
 import {s} from 'lib/styles'
 import {niceDate} from 'lib/strings/time'
@@ -21,10 +20,10 @@ import {sanitizeHandle} from 'lib/strings/handles'
 import {countLines, pluralize} from 'lib/strings/helpers'
 import {isEmbedByEmbedder} from 'lib/embeds'
 import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
-import {useStores} from 'state/index'
 import {PostMeta} from '../util/PostMeta'
 import {PostEmbeds} from '../util/post-embeds'
 import {PostCtrls} from '../util/post-ctrls/PostCtrls'
+import {PostDropdownBtn} from '../util/forms/PostDropdownBtn'
 import {PostHider} from '../util/moderation/PostHider'
 import {ContentHider} from '../util/moderation/ContentHider'
 import {PostAlerts} from '../util/moderation/PostAlerts'
@@ -36,125 +35,172 @@ import {TimeElapsed} from 'view/com/util/TimeElapsed'
 import {makeProfileLink} from 'lib/routes/links'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {MAX_POST_LINES} from 'lib/constants'
-import {logger} from '#/logger'
+import {Trans, msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useLanguagePrefs} from '#/state/preferences'
+import {useComposerControls} from '#/state/shell/composer'
+import {useModerationOpts} from '#/state/queries/preferences'
+import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
 
-export const PostThreadItem = observer(function PostThreadItem({
-  item,
-  onPostReply,
-  hasPrecedingItem,
+export function PostThreadItem({
+  post,
+  record,
   treeView,
+  depth,
+  isHighlightedPost,
+  hasMore,
+  showChildReplyLine,
+  showParentReplyLine,
+  hasPrecedingItem,
+  onPostReply,
 }: {
-  item: PostThreadItemModel
-  onPostReply: () => void
-  hasPrecedingItem: boolean
+  post: AppBskyFeedDefs.PostView
+  record: AppBskyFeedPost.Record
   treeView: boolean
+  depth: number
+  isHighlightedPost?: boolean
+  hasMore?: boolean
+  showChildReplyLine?: boolean
+  showParentReplyLine?: boolean
+  hasPrecedingItem: boolean
+  onPostReply: () => void
 }) {
+  const moderationOpts = useModerationOpts()
+  const postShadowed = usePostShadow(post)
+  const richText = useMemo(
+    () =>
+      new RichTextAPI({
+        text: record.text,
+        facets: record.facets,
+      }),
+    [record],
+  )
+  const moderation = useMemo(
+    () =>
+      post && moderationOpts ? moderatePost(post, moderationOpts) : undefined,
+    [post, moderationOpts],
+  )
+  if (postShadowed === POST_TOMBSTONE) {
+    return <PostThreadItemDeleted />
+  }
+  if (richText && moderation) {
+    return (
+      <PostThreadItemLoaded
+        post={postShadowed}
+        record={record}
+        richText={richText}
+        moderation={moderation}
+        treeView={treeView}
+        depth={depth}
+        isHighlightedPost={isHighlightedPost}
+        hasMore={hasMore}
+        showChildReplyLine={showChildReplyLine}
+        showParentReplyLine={showParentReplyLine}
+        hasPrecedingItem={hasPrecedingItem}
+        onPostReply={onPostReply}
+      />
+    )
+  }
+  return null
+}
+
+function PostThreadItemDeleted() {
+  const styles = useStyles()
+  const pal = usePalette('default')
+  return (
+    <View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}>
+      <FontAwesomeIcon icon={['far', 'trash-can']} color={pal.colors.icon} />
+      <Text style={[pal.textLight, s.ml10]}>
+        <Trans>This post has been deleted.</Trans>
+      </Text>
+    </View>
+  )
+}
+
+let PostThreadItemLoaded = ({
+  post,
+  record,
+  richText,
+  moderation,
+  treeView,
+  depth,
+  isHighlightedPost,
+  hasMore,
+  showChildReplyLine,
+  showParentReplyLine,
+  hasPrecedingItem,
+  onPostReply,
+}: {
+  post: Shadow<AppBskyFeedDefs.PostView>
+  record: AppBskyFeedPost.Record
+  richText: RichTextAPI
+  moderation: PostModeration
+  treeView: boolean
+  depth: number
+  isHighlightedPost?: boolean
+  hasMore?: boolean
+  showChildReplyLine?: boolean
+  showParentReplyLine?: boolean
+  hasPrecedingItem: boolean
+  onPostReply: () => void
+}): React.ReactNode => {
   const pal = usePalette('default')
-  const store = useStores()
-  const [deleted, setDeleted] = React.useState(false)
+  const langPrefs = useLanguagePrefs()
+  const {openComposer} = useComposerControls()
   const [limitLines, setLimitLines] = React.useState(
-    countLines(item.richText?.text) >= MAX_POST_LINES,
+    () => countLines(richText?.text) >= MAX_POST_LINES,
   )
   const styles = useStyles()
-  const record = item.postRecord
-  const hasEngagement = item.post.likeCount || item.post.repostCount
+  const hasEngagement = post.likeCount || post.repostCount
 
-  const itemUri = item.post.uri
-  const itemCid = item.post.cid
-  const itemHref = React.useMemo(() => {
-    const urip = new AtUri(item.post.uri)
-    return makeProfileLink(item.post.author, 'post', urip.rkey)
-  }, [item.post.uri, item.post.author])
-  const itemTitle = `Post by ${item.post.author.handle}`
-  const authorHref = makeProfileLink(item.post.author)
-  const authorTitle = item.post.author.handle
-  const isAuthorMuted = item.post.author.viewer?.muted
+  const rootUri = record.reply?.root?.uri || post.uri
+  const postHref = React.useMemo(() => {
+    const urip = new AtUri(post.uri)
+    return makeProfileLink(post.author, 'post', urip.rkey)
+  }, [post.uri, post.author])
+  const itemTitle = `Post by ${post.author.handle}`
+  const authorHref = makeProfileLink(post.author)
+  const authorTitle = post.author.handle
+  const isAuthorMuted = post.author.viewer?.muted
   const likesHref = React.useMemo(() => {
-    const urip = new AtUri(item.post.uri)
-    return makeProfileLink(item.post.author, 'post', urip.rkey, 'liked-by')
-  }, [item.post.uri, item.post.author])
+    const urip = new AtUri(post.uri)
+    return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by')
+  }, [post.uri, post.author])
   const likesTitle = 'Likes on this post'
   const repostsHref = React.useMemo(() => {
-    const urip = new AtUri(item.post.uri)
-    return makeProfileLink(item.post.author, 'post', urip.rkey, 'reposted-by')
-  }, [item.post.uri, item.post.author])
+    const urip = new AtUri(post.uri)
+    return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
+  }, [post.uri, post.author])
   const repostsTitle = 'Reposts of this post'
 
   const translatorUrl = getTranslatorLink(
     record?.text || '',
-    store.preferences.primaryLanguage,
+    langPrefs.primaryLanguage,
   )
   const needsTranslation = useMemo(
     () =>
       Boolean(
-        store.preferences.primaryLanguage &&
-          !isPostInLanguage(item.post, [store.preferences.primaryLanguage]),
+        langPrefs.primaryLanguage &&
+          !isPostInLanguage(post, [langPrefs.primaryLanguage]),
       ),
-    [item.post, store.preferences.primaryLanguage],
+    [post, langPrefs.primaryLanguage],
   )
 
   const onPressReply = React.useCallback(() => {
-    store.shell.openComposer({
+    openComposer({
       replyTo: {
-        uri: item.post.uri,
-        cid: item.post.cid,
-        text: record?.text as string,
+        uri: post.uri,
+        cid: post.cid,
+        text: record.text,
         author: {
-          handle: item.post.author.handle,
-          displayName: item.post.author.displayName,
-          avatar: item.post.author.avatar,
+          handle: post.author.handle,
+          displayName: post.author.displayName,
+          avatar: post.author.avatar,
         },
       },
       onPost: onPostReply,
     })
-  }, [store, item, record, onPostReply])
-
-  const onPressToggleRepost = React.useCallback(() => {
-    return item
-      .toggleRepost()
-      .catch(e => logger.error('Failed to toggle repost', {error: e}))
-  }, [item])
-
-  const onPressToggleLike = React.useCallback(() => {
-    return item
-      .toggleLike()
-      .catch(e => logger.error('Failed to toggle like', {error: e}))
-  }, [item])
-
-  const onCopyPostText = React.useCallback(() => {
-    Clipboard.setString(record?.text || '')
-    Toast.show('Copied to clipboard')
-  }, [record])
-
-  const onOpenTranslate = React.useCallback(() => {
-    Linking.openURL(translatorUrl)
-  }, [translatorUrl])
-
-  const onToggleThreadMute = React.useCallback(async () => {
-    try {
-      await item.toggleThreadMute()
-      if (item.isThreadMuted) {
-        Toast.show('You will no longer receive notifications for this thread')
-      } else {
-        Toast.show('You will now receive notifications for this thread')
-      }
-    } catch (e) {
-      logger.error('Failed to toggle thread mute', {error: e})
-    }
-  }, [item])
-
-  const onDeletePost = React.useCallback(() => {
-    item.delete().then(
-      () => {
-        setDeleted(true)
-        Toast.show('Post deleted')
-      },
-      e => {
-        logger.error('Failed to delete post', {error: e})
-        Toast.show('Failed to delete post, please try again')
-      },
-    )
-  }, [item])
+  }, [openComposer, post, record, onPostReply])
 
   const onPressShowMore = React.useCallback(() => {
     setLimitLines(false)
@@ -164,22 +210,10 @@ export const PostThreadItem = observer(function PostThreadItem({
     return <ErrorMessage message="Invalid or unsupported post record" />
   }
 
-  if (deleted) {
-    return (
-      <View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}>
-        <FontAwesomeIcon
-          icon={['far', 'trash-can']}
-          style={pal.icon as FontAwesomeIconStyle}
-        />
-        <Text style={[pal.textLight, s.ml10]}>This post has been deleted.</Text>
-      </View>
-    )
-  }
-
-  if (item._isHighlightedPost) {
+  if (isHighlightedPost) {
     return (
       <>
-        {item.rootUri !== item.uri && (
+        {rootUri !== post.uri && (
           <View style={{paddingLeft: 16, flexDirection: 'row', height: 16}}>
             <View style={{width: 38}}>
               <View
@@ -196,7 +230,7 @@ export const PostThreadItem = observer(function PostThreadItem({
         )}
 
         <Link
-          testID={`postThreadItem-by-${item.post.author.handle}`}
+          testID={`postThreadItem-by-${post.author.handle}`}
           style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
           noFeedback
           accessible={false}>
@@ -205,10 +239,10 @@ export const PostThreadItem = observer(function PostThreadItem({
             <View style={[styles.layoutAvi, {paddingBottom: 8}]}>
               <PreviewableUserAvatar
                 size={52}
-                did={item.post.author.did}
-                handle={item.post.author.handle}
-                avatar={item.post.author.avatar}
-                moderation={item.moderation.avatar}
+                did={post.author.did}
+                handle={post.author.handle}
+                avatar={post.author.avatar}
+                moderation={moderation.avatar}
               />
             </View>
             <View style={styles.layoutContent}>
@@ -225,17 +259,17 @@ export const PostThreadItem = observer(function PostThreadItem({
                       numberOfLines={1}
                       lineHeight={1.2}>
                       {sanitizeDisplayName(
-                        item.post.author.displayName ||
-                          sanitizeHandle(item.post.author.handle),
+                        post.author.displayName ||
+                          sanitizeHandle(post.author.handle),
                       )}
                     </Text>
                   </Link>
-                  <TimeElapsed timestamp={item.post.indexedAt}>
+                  <TimeElapsed timestamp={post.indexedAt}>
                     {({timeElapsed}) => (
                       <Text
                         type="md"
                         style={[styles.metaItem, pal.textLight]}
-                        title={niceDate(item.post.indexedAt)}>
+                        title={niceDate(post.indexedAt)}>
                         &middot;&nbsp;{timeElapsed}
                       </Text>
                     )}
@@ -272,23 +306,15 @@ export const PostThreadItem = observer(function PostThreadItem({
                   href={authorHref}
                   title={authorTitle}>
                   <Text type="md" style={[pal.textLight]} numberOfLines={1}>
-                    {sanitizeHandle(item.post.author.handle, '@')}
+                    {sanitizeHandle(post.author.handle, '@')}
                   </Text>
                 </Link>
               </View>
             </View>
             <PostDropdownBtn
               testID="postDropdownBtn"
-              itemUri={itemUri}
-              itemCid={itemCid}
-              itemHref={itemHref}
-              itemTitle={itemTitle}
-              isAuthor={item.post.author.did === store.me.did}
-              isThreadMuted={item.isThreadMuted}
-              onCopyPostText={onCopyPostText}
-              onOpenTranslate={onOpenTranslate}
-              onToggleThreadMute={onToggleThreadMute}
-              onDeletePost={onDeletePost}
+              post={post}
+              record={record}
               style={{
                 paddingVertical: 6,
                 paddingHorizontal: 10,
@@ -299,16 +325,16 @@ export const PostThreadItem = observer(function PostThreadItem({
           </View>
           <View style={[s.pl10, s.pr10, s.pb10]}>
             <ContentHider
-              moderation={item.moderation.content}
+              moderation={moderation.content}
               ignoreMute
               style={styles.contentHider}
               childContainerStyle={styles.contentHiderChild}>
               <PostAlerts
-                moderation={item.moderation.content}
+                moderation={moderation.content}
                 includeMute
                 style={styles.alert}
               />
-              {item.richText?.text ? (
+              {richText?.text ? (
                 <View
                   style={[
                     styles.postTextContainer,
@@ -316,59 +342,56 @@ export const PostThreadItem = observer(function PostThreadItem({
                   ]}>
                   <RichText
                     type="post-text-lg"
-                    richText={item.richText}
+                    richText={richText}
                     lineHeight={1.3}
                     style={s.flex1}
                   />
                 </View>
               ) : undefined}
-              {item.post.embed && (
+              {post.embed && (
                 <ContentHider
-                  moderation={item.moderation.embed}
-                  ignoreMute={isEmbedByEmbedder(
-                    item.post.embed,
-                    item.post.author.did,
-                  )}
+                  moderation={moderation.embed}
+                  ignoreMute={isEmbedByEmbedder(post.embed, post.author.did)}
                   style={s.mb10}>
                   <PostEmbeds
-                    embed={item.post.embed}
-                    moderation={item.moderation.embed}
+                    embed={post.embed}
+                    moderation={moderation.embed}
                   />
                 </ContentHider>
               )}
             </ContentHider>
             <ExpandedPostDetails
-              post={item.post}
+              post={post}
               translatorUrl={translatorUrl}
               needsTranslation={needsTranslation}
             />
             {hasEngagement ? (
               <View style={[styles.expandedInfo, pal.border]}>
-                {item.post.repostCount ? (
+                {post.repostCount ? (
                   <Link
                     style={styles.expandedInfoItem}
                     href={repostsHref}
                     title={repostsTitle}>
                     <Text testID="repostCount" type="lg" style={pal.textLight}>
                       <Text type="xl-bold" style={pal.text}>
-                        {formatCount(item.post.repostCount)}
+                        {formatCount(post.repostCount)}
                       </Text>{' '}
-                      {pluralize(item.post.repostCount, 'repost')}
+                      {pluralize(post.repostCount, 'repost')}
                     </Text>
                   </Link>
                 ) : (
                   <></>
                 )}
-                {item.post.likeCount ? (
+                {post.likeCount ? (
                   <Link
                     style={styles.expandedInfoItem}
                     href={likesHref}
                     title={likesTitle}>
                     <Text testID="likeCount" type="lg" style={pal.textLight}>
                       <Text type="xl-bold" style={pal.text}>
-                        {formatCount(item.post.likeCount)}
+                        {formatCount(post.likeCount)}
                       </Text>{' '}
-                      {pluralize(item.post.likeCount, 'like')}
+                      {pluralize(post.likeCount, 'like')}
                     </Text>
                   </Link>
                 ) : (
@@ -381,24 +404,9 @@ export const PostThreadItem = observer(function PostThreadItem({
             <View style={[s.pl10, s.pb5]}>
               <PostCtrls
                 big
-                itemUri={itemUri}
-                itemCid={itemCid}
-                itemHref={itemHref}
-                itemTitle={itemTitle}
-                author={item.post.author}
-                text={item.richText?.text || record.text}
-                indexedAt={item.post.indexedAt}
-                isAuthor={item.post.author.did === store.me.did}
-                isReposted={!!item.post.viewer?.repost}
-                isLiked={!!item.post.viewer?.like}
-                isThreadMuted={item.isThreadMuted}
+                post={post}
+                record={record}
                 onPressReply={onPressReply}
-                onPressToggleRepost={onPressToggleRepost}
-                onPressToggleLike={onPressToggleLike}
-                onCopyPostText={onCopyPostText}
-                onOpenTranslate={onOpenTranslate}
-                onToggleThreadMute={onToggleThreadMute}
-                onDeletePost={onDeletePost}
               />
             </View>
           </View>
@@ -406,17 +414,19 @@ export const PostThreadItem = observer(function PostThreadItem({
       </>
     )
   } else {
-    const isThreadedChild = treeView && item._depth > 1
+    const isThreadedChild = treeView && depth > 1
     return (
       <PostOuterWrapper
-        item={item}
-        hasPrecedingItem={hasPrecedingItem}
-        treeView={treeView}>
+        post={post}
+        depth={depth}
+        showParentReplyLine={!!showParentReplyLine}
+        treeView={treeView}
+        hasPrecedingItem={hasPrecedingItem}>
         <PostHider
-          testID={`postThreadItem-by-${item.post.author.handle}`}
-          href={itemHref}
+          testID={`postThreadItem-by-${post.author.handle}`}
+          href={postHref}
           style={[pal.view]}
-          moderation={item.moderation.content}>
+          moderation={moderation.content}>
           <PostSandboxWarning />
 
           <View
@@ -427,7 +437,7 @@ export const PostThreadItem = observer(function PostThreadItem({
               height: isThreadedChild ? 8 : 16,
             }}>
             <View style={{width: 38}}>
-              {!isThreadedChild && item._showParentReplyLine && (
+              {!isThreadedChild && showParentReplyLine && (
                 <View
                   style={[
                     styles.replyLine,
@@ -446,21 +456,20 @@ export const PostThreadItem = observer(function PostThreadItem({
             style={[
               styles.layout,
               {
-                paddingBottom:
-                  item._showChildReplyLine && !isThreadedChild ? 0 : 8,
+                paddingBottom: showChildReplyLine && !isThreadedChild ? 0 : 8,
               },
             ]}>
             {!isThreadedChild && (
               <View style={styles.layoutAvi}>
                 <PreviewableUserAvatar
                   size={38}
-                  did={item.post.author.did}
-                  handle={item.post.author.handle}
-                  avatar={item.post.author.avatar}
-                  moderation={item.moderation.avatar}
+                  did={post.author.did}
+                  handle={post.author.handle}
+                  avatar={post.author.avatar}
+                  moderation={moderation.avatar}
                 />
 
-                {item._showChildReplyLine && (
+                {showChildReplyLine && (
                   <View
                     style={[
                       styles.replyLine,
@@ -477,10 +486,10 @@ export const PostThreadItem = observer(function PostThreadItem({
 
             <View style={styles.layoutContent}>
               <PostMeta
-                author={item.post.author}
-                authorHasWarning={!!item.post.author.labels?.length}
-                timestamp={item.post.indexedAt}
-                postHref={itemHref}
+                author={post.author}
+                authorHasWarning={!!post.author.labels?.length}
+                timestamp={post.indexedAt}
+                postHref={postHref}
                 showAvatar={isThreadedChild}
                 avatarSize={26}
                 displayNameType="md-bold"
@@ -488,14 +497,14 @@ export const PostThreadItem = observer(function PostThreadItem({
                 style={isThreadedChild && s.mb5}
               />
               <PostAlerts
-                moderation={item.moderation.content}
+                moderation={moderation.content}
                 style={styles.alert}
               />
-              {item.richText?.text ? (
+              {richText?.text ? (
                 <View style={styles.postTextContainer}>
                   <RichText
                     type="post-text"
-                    richText={item.richText}
+                    richText={richText}
                     style={[pal.text, s.flex1]}
                     lineHeight={1.3}
                     numberOfLines={limitLines ? MAX_POST_LINES : undefined}
@@ -510,42 +519,24 @@ export const PostThreadItem = observer(function PostThreadItem({
                   href="#"
                 />
               ) : undefined}
-              {item.post.embed && (
+              {post.embed && (
                 <ContentHider
                   style={styles.contentHider}
-                  moderation={item.moderation.embed}>
+                  moderation={moderation.embed}>
                   <PostEmbeds
-                    embed={item.post.embed}
-                    moderation={item.moderation.embed}
+                    embed={post.embed}
+                    moderation={moderation.embed}
                   />
                 </ContentHider>
               )}
               <PostCtrls
-                itemUri={itemUri}
-                itemCid={itemCid}
-                itemHref={itemHref}
-                itemTitle={itemTitle}
-                author={item.post.author}
-                text={item.richText?.text || record.text}
-                indexedAt={item.post.indexedAt}
-                isAuthor={item.post.author.did === store.me.did}
-                replyCount={item.post.replyCount}
-                repostCount={item.post.repostCount}
-                likeCount={item.post.likeCount}
-                isReposted={!!item.post.viewer?.repost}
-                isLiked={!!item.post.viewer?.like}
-                isThreadMuted={item.isThreadMuted}
+                post={post}
+                record={record}
                 onPressReply={onPressReply}
-                onPressToggleRepost={onPressToggleRepost}
-                onPressToggleLike={onPressToggleLike}
-                onCopyPostText={onCopyPostText}
-                onOpenTranslate={onOpenTranslate}
-                onToggleThreadMute={onToggleThreadMute}
-                onDeletePost={onDeletePost}
               />
             </View>
           </View>
-          {item._hasMore ? (
+          {hasMore ? (
             <Link
               style={[
                 styles.loadMore,
@@ -555,7 +546,7 @@ export const PostThreadItem = observer(function PostThreadItem({
                   paddingBottom: treeView ? 4 : 12,
                 },
               ]}
-              href={itemHref}
+              href={postHref}
               title={itemTitle}
               noFeedback>
               <Text type="sm-medium" style={pal.textLight}>
@@ -572,22 +563,27 @@ export const PostThreadItem = observer(function PostThreadItem({
       </PostOuterWrapper>
     )
   }
-})
+}
+PostThreadItemLoaded = memo(PostThreadItemLoaded)
 
 function PostOuterWrapper({
-  item,
-  hasPrecedingItem,
+  post,
   treeView,
+  depth,
+  showParentReplyLine,
+  hasPrecedingItem,
   children,
 }: React.PropsWithChildren<{
-  item: PostThreadItemModel
-  hasPrecedingItem: boolean
+  post: AppBskyFeedDefs.PostView
   treeView: boolean
+  depth: number
+  showParentReplyLine: boolean
+  hasPrecedingItem: boolean
 }>) {
   const {isMobile} = useWebMediaQueries()
   const pal = usePalette('default')
   const styles = useStyles()
-  if (treeView && item._depth > 1) {
+  if (treeView && depth > 1) {
     return (
       <View
         style={[
@@ -597,13 +593,13 @@ function PostOuterWrapper({
           {
             flexDirection: 'row',
             paddingLeft: 20,
-            borderTopWidth: item._depth === 1 ? 1 : 0,
-            paddingTop: item._depth === 1 ? 8 : 0,
+            borderTopWidth: depth === 1 ? 1 : 0,
+            paddingTop: depth === 1 ? 8 : 0,
           },
         ]}>
-        {Array.from(Array(item._depth - 1)).map((_, n: number) => (
+        {Array.from(Array(depth - 1)).map((_, n: number) => (
           <View
-            key={`${item.uri}-padding-${n}`}
+            key={`${post.uri}-padding-${n}`}
             style={{
               borderLeftWidth: 2,
               borderLeftColor: pal.colors.border,
@@ -622,7 +618,7 @@ function PostOuterWrapper({
         styles.outer,
         pal.view,
         pal.border,
-        item._showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
+        showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
         styles.cursor,
       ]}>
       {children}
@@ -640,14 +636,17 @@ function ExpandedPostDetails({
   translatorUrl: string
 }) {
   const pal = usePalette('default')
+  const {_} = useLingui()
   return (
     <View style={[s.flexRow, s.mt2, s.mb10]}>
       <Text style={pal.textLight}>{niceDate(post.indexedAt)}</Text>
       {needsTranslation && (
         <>
-          <Text style={pal.textLight}> • </Text>
-          <Link href={translatorUrl} title="Translate">
-            <Text style={pal.link}>Translate</Text>
+          <Text style={[pal.textLight, s.ml5, s.mr5]}>•</Text>
+          <Link href={translatorUrl} title={_(msg`Translate`)}>
+            <Text style={pal.link}>
+              <Trans>Translate</Trans>
+            </Text>
           </Link>
         </>
       )}