about summary refs log tree commit diff
path: root/src/view/com/post/Post.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/post/Post.tsx')
-rw-r--r--src/view/com/post/Post.tsx272
1 files changed, 88 insertions, 184 deletions
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 667584f68..4a5b8041e 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -1,19 +1,14 @@
-import React, {useState} from 'react'
+import React, {useState, useMemo} from 'react'
+import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
 import {
-  ActivityIndicator,
-  Linking,
-  StyleProp,
-  StyleSheet,
-  View,
-  ViewStyle,
-} from 'react-native'
-import {AppBskyFeedPost as FeedPost} from '@atproto/api'
-import {observer} from 'mobx-react-lite'
-import Clipboard from '@react-native-clipboard/clipboard'
-import {AtUri} from '@atproto/api'
+  AppBskyFeedDefs,
+  AppBskyFeedPost,
+  AtUri,
+  moderatePost,
+  PostModeration,
+  RichText as RichTextAPI,
+} from '@atproto/api'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {PostThreadModel} from 'state/models/content/post-thread'
-import {PostThreadItemModel} from 'state/models/content/post-thread-item'
 import {Link, TextLink} from '../util/Link'
 import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
@@ -23,174 +18,111 @@ import {ContentHider} from '../util/moderation/ContentHider'
 import {PostAlerts} from '../util/moderation/PostAlerts'
 import {Text} from '../util/text/Text'
 import {RichText} from '../util/text/RichText'
-import * as Toast from '../util/Toast'
 import {PreviewableUserAvatar} from '../util/UserAvatar'
 import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
-import {getTranslatorLink} from '../../../locale/helpers'
 import {makeProfileLink} from 'lib/routes/links'
 import {MAX_POST_LINES} from 'lib/constants'
 import {countLines} from 'lib/strings/helpers'
-import {logger} from '#/logger'
-import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads'
-import {useLanguagePrefs} from '#/state/preferences'
+import {useModerationOpts} from '#/state/queries/preferences'
+import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
 
-export const Post = observer(function PostImpl({
-  view,
+export function Post({
+  post,
+  dataUpdatedAt,
   showReplyLine,
-  hideError,
   style,
 }: {
-  view: PostThreadModel
+  post: AppBskyFeedDefs.PostView
+  dataUpdatedAt: number
   showReplyLine?: boolean
-  hideError?: boolean
   style?: StyleProp<ViewStyle>
 }) {
-  const pal = usePalette('default')
-  const [deleted, setDeleted] = useState(false)
-
-  // deleted
-  // =
-  if (deleted) {
-    return <View />
-  }
-
-  // loading
-  // =
-  if (!view.hasContent && view.isLoading) {
-    return (
-      <View style={pal.view}>
-        <ActivityIndicator />
-      </View>
-    )
+  const moderationOpts = useModerationOpts()
+  const record = useMemo<AppBskyFeedPost.Record | undefined>(
+    () =>
+      AppBskyFeedPost.isRecord(post.record) &&
+      AppBskyFeedPost.validateRecord(post.record).success
+        ? post.record
+        : undefined,
+    [post],
+  )
+  const postShadowed = usePostShadow(post, dataUpdatedAt)
+  const richText = useMemo(
+    () =>
+      record
+        ? new RichTextAPI({
+            text: record.text,
+            facets: record.facets,
+          })
+        : undefined,
+    [record],
+  )
+  const moderation = useMemo(
+    () => (moderationOpts ? moderatePost(post, moderationOpts) : undefined),
+    [moderationOpts, post],
+  )
+  if (postShadowed === POST_TOMBSTONE) {
+    return null
   }
-
-  // error
-  // =
-  if (view.hasError || !view.thread || !view.thread?.postRecord) {
-    if (hideError) {
-      return <View />
-    }
+  if (record && richText && moderation) {
     return (
-      <View style={pal.view}>
-        <Text>{view.error || 'Thread not found'}</Text>
-      </View>
+      <PostInner
+        post={postShadowed}
+        record={record}
+        richText={richText}
+        moderation={moderation}
+        showReplyLine={showReplyLine}
+        style={style}
+      />
     )
   }
+  return null
+}
 
-  // loaded
-  // =
-
-  return (
-    <PostLoaded
-      item={view.thread}
-      record={view.thread.postRecord}
-      setDeleted={setDeleted}
-      showReplyLine={showReplyLine}
-      style={style}
-    />
-  )
-})
-
-const PostLoaded = observer(function PostLoadedImpl({
-  item,
+function PostInner({
+  post,
   record,
-  setDeleted,
+  richText,
+  moderation,
   showReplyLine,
   style,
 }: {
-  item: PostThreadItemModel
-  record: FeedPost.Record
-  setDeleted: (v: boolean) => void
+  post: AppBskyFeedDefs.PostView
+  record: AppBskyFeedPost.Record
+  richText: RichTextAPI
+  moderation: PostModeration
   showReplyLine?: boolean
   style?: StyleProp<ViewStyle>
 }) {
   const pal = usePalette('default')
   const store = useStores()
-  const mutedThreads = useMutedThreads()
-  const toggleThreadMute = useToggleThreadMute()
-  const langPrefs = useLanguagePrefs()
-  const [limitLines, setLimitLines] = React.useState(
-    countLines(item.richText?.text) >= MAX_POST_LINES,
+  const [limitLines, setLimitLines] = useState(
+    countLines(richText?.text) >= MAX_POST_LINES,
   )
-  const itemUri = item.post.uri
-  const itemCid = item.post.cid
-  const itemUrip = new AtUri(item.post.uri)
-  const itemHref = makeProfileLink(item.post.author, 'post', itemUrip.rkey)
-  const itemTitle = `Post by ${item.post.author.handle}`
+  const itemUrip = new AtUri(post.uri)
+  const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey)
   let replyAuthorDid = ''
   if (record.reply) {
     const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri)
     replyAuthorDid = urip.hostname
   }
 
-  const translatorUrl = getTranslatorLink(
-    record?.text || '',
-    langPrefs.primaryLanguage,
-  )
-
   const onPressReply = React.useCallback(() => {
     store.shell.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,
         },
       },
     })
-  }, [store, item, record])
-
-  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(() => {
-    try {
-      const muted = toggleThreadMute(item.data.rootUri)
-      if (muted) {
-        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, toggleThreadMute])
-
-  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, setDeleted])
+  }, [store, post, record])
 
   const onPressShowMore = React.useCallback(() => {
     setLimitLines(false)
@@ -203,17 +135,17 @@ const PostLoaded = observer(function PostLoadedImpl({
         <View style={styles.layoutAvi}>
           <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}>
           <PostMeta
-            author={item.post.author}
-            authorHasWarning={!!item.post.author.labels?.length}
-            timestamp={item.post.indexedAt}
+            author={post.author}
+            authorHasWarning={!!post.author.labels?.length}
+            timestamp={post.indexedAt}
             postHref={itemHref}
           />
           {replyAuthorDid !== '' && (
@@ -239,19 +171,16 @@ const PostLoaded = observer(function PostLoadedImpl({
             </View>
           )}
           <ContentHider
-            moderation={item.moderation.content}
+            moderation={moderation.content}
             style={styles.contentHider}
             childContainerStyle={styles.contentHiderChild}>
-            <PostAlerts
-              moderation={item.moderation.content}
-              style={styles.alert}
-            />
-            {item.richText?.text ? (
+            <PostAlerts moderation={moderation.content} style={styles.alert} />
+            {richText.text ? (
               <View style={styles.postTextContainer}>
                 <RichText
                   testID="postText"
                   type="post-text"
-                  richText={item.richText}
+                  richText={richText}
                   lineHeight={1.3}
                   numberOfLines={limitLines ? MAX_POST_LINES : undefined}
                   style={s.flex1}
@@ -266,45 +195,20 @@ const PostLoaded = observer(function PostLoadedImpl({
                 href="#"
               />
             ) : undefined}
-            {item.post.embed ? (
+            {post.embed ? (
               <ContentHider
-                moderation={item.moderation.embed}
+                moderation={moderation.embed}
                 style={styles.contentHider}>
-                <PostEmbeds
-                  embed={item.post.embed}
-                  moderation={item.moderation.embed}
-                />
+                <PostEmbeds embed={post.embed} moderation={moderation.embed} />
               </ContentHider>
             ) : null}
           </ContentHider>
-          <PostCtrls
-            itemUri={itemUri}
-            itemCid={itemCid}
-            itemHref={itemHref}
-            itemTitle={itemTitle}
-            author={item.post.author}
-            indexedAt={item.post.indexedAt}
-            text={item.richText?.text || record.text}
-            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={mutedThreads.includes(item.data.rootUri)}
-            onPressReply={onPressReply}
-            onPressToggleRepost={onPressToggleRepost}
-            onPressToggleLike={onPressToggleLike}
-            onCopyPostText={onCopyPostText}
-            onOpenTranslate={onOpenTranslate}
-            onToggleThreadMute={onToggleThreadMute}
-            onDeletePost={onDeletePost}
-          />
+          <PostCtrls post={post} record={record} onPressReply={onPressReply} />
         </View>
       </View>
     </Link>
   )
-})
+}
 
 const styles = StyleSheet.create({
   outer: {