about summary refs log tree commit diff
path: root/src/view/com/posts/FeedItem.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/posts/FeedItem.tsx')
-rw-r--r--src/view/com/posts/FeedItem.tsx653
1 files changed, 0 insertions, 653 deletions
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
deleted file mode 100644
index c04921c68..000000000
--- a/src/view/com/posts/FeedItem.tsx
+++ /dev/null
@@ -1,653 +0,0 @@
-import React, {memo, useMemo, useState} from 'react'
-import {StyleSheet, View} from 'react-native'
-import {
-  AppBskyActorDefs,
-  AppBskyFeedDefs,
-  AppBskyFeedPost,
-  AppBskyFeedThreadgate,
-  AtUri,
-  ModerationDecision,
-  RichText as RichTextAPI,
-} from '@atproto/api'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useQueryClient} from '@tanstack/react-query'
-
-import {isReasonFeedSource, ReasonFeedSource} from '#/lib/api/feed/types'
-import {MAX_POST_LINES} from '#/lib/constants'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {makeProfileLink} from '#/lib/routes/links'
-import {sanitizeDisplayName} from '#/lib/strings/display-names'
-import {sanitizeHandle} from '#/lib/strings/handles'
-import {countLines} from '#/lib/strings/helpers'
-import {s} from '#/lib/styles'
-import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
-import {useFeedFeedbackContext} from '#/state/feed-feedback'
-import {precacheProfile} from '#/state/queries/profile'
-import {useSession} from '#/state/session'
-import {useComposerControls} from '#/state/shell/composer'
-import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
-import {FeedNameText} from '#/view/com/util/FeedInfoText'
-import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
-import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
-import {PostMeta} from '#/view/com/util/PostMeta'
-import {Text} from '#/view/com/util/text/Text'
-import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a} from '#/alf'
-import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
-import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
-import {ContentHider} from '#/components/moderation/ContentHider'
-import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
-import {PostAlerts} from '#/components/moderation/PostAlerts'
-import {AppModerationCause} from '#/components/Pills'
-import {ProfileHoverCard} from '#/components/ProfileHoverCard'
-import {RichText} from '#/components/RichText'
-import {SubtleWebHover} from '#/components/SubtleWebHover'
-import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
-import {AviFollowButton} from './AviFollowButton'
-
-interface FeedItemProps {
-  record: AppBskyFeedPost.Record
-  reason:
-    | AppBskyFeedDefs.ReasonRepost
-    | AppBskyFeedDefs.ReasonPin
-    | ReasonFeedSource
-    | {[k: string]: unknown; $type: string}
-    | undefined
-  moderation: ModerationDecision
-  parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
-  showReplyTo: boolean
-  isThreadChild?: boolean
-  isThreadLastChild?: boolean
-  isThreadParent?: boolean
-  feedContext: string | undefined
-  hideTopBorder?: boolean
-  isParentBlocked?: boolean
-  isParentNotFound?: boolean
-}
-
-export function FeedItem({
-  post,
-  record,
-  reason,
-  feedContext,
-  moderation,
-  parentAuthor,
-  showReplyTo,
-  isThreadChild,
-  isThreadLastChild,
-  isThreadParent,
-  hideTopBorder,
-  isParentBlocked,
-  isParentNotFound,
-  rootPost,
-}: FeedItemProps & {
-  post: AppBskyFeedDefs.PostView
-  rootPost: AppBskyFeedDefs.PostView
-}): React.ReactNode {
-  const postShadowed = usePostShadow(post)
-  const richText = useMemo(
-    () =>
-      new RichTextAPI({
-        text: record.text,
-        facets: record.facets,
-      }),
-    [record],
-  )
-  if (postShadowed === POST_TOMBSTONE) {
-    return null
-  }
-  if (richText && moderation) {
-    return (
-      <FeedItemInner
-        // Safeguard from clobbering per-post state below:
-        key={postShadowed.uri}
-        post={postShadowed}
-        record={record}
-        reason={reason}
-        feedContext={feedContext}
-        richText={richText}
-        parentAuthor={parentAuthor}
-        showReplyTo={showReplyTo}
-        moderation={moderation}
-        isThreadChild={isThreadChild}
-        isThreadLastChild={isThreadLastChild}
-        isThreadParent={isThreadParent}
-        hideTopBorder={hideTopBorder}
-        isParentBlocked={isParentBlocked}
-        isParentNotFound={isParentNotFound}
-        rootPost={rootPost}
-      />
-    )
-  }
-  return null
-}
-
-let FeedItemInner = ({
-  post,
-  record,
-  reason,
-  feedContext,
-  richText,
-  moderation,
-  parentAuthor,
-  showReplyTo,
-  isThreadChild,
-  isThreadLastChild,
-  isThreadParent,
-  hideTopBorder,
-  isParentBlocked,
-  isParentNotFound,
-  rootPost,
-}: FeedItemProps & {
-  richText: RichTextAPI
-  post: Shadow<AppBskyFeedDefs.PostView>
-  rootPost: AppBskyFeedDefs.PostView
-}): React.ReactNode => {
-  const queryClient = useQueryClient()
-  const {openComposer} = useComposerControls()
-  const pal = usePalette('default')
-  const {_} = useLingui()
-
-  const href = useMemo(() => {
-    const urip = new AtUri(post.uri)
-    return makeProfileLink(post.author, 'post', urip.rkey)
-  }, [post.uri, post.author])
-  const {sendInteraction} = useFeedFeedbackContext()
-
-  const onPressReply = React.useCallback(() => {
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#interactionReply',
-      feedContext,
-    })
-    openComposer({
-      replyTo: {
-        uri: post.uri,
-        cid: post.cid,
-        text: record.text || '',
-        author: post.author,
-        embed: post.embed,
-        moderation,
-      },
-    })
-  }, [post, record, openComposer, moderation, sendInteraction, feedContext])
-
-  const onOpenAuthor = React.useCallback(() => {
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#clickthroughAuthor',
-      feedContext,
-    })
-  }, [sendInteraction, post, feedContext])
-
-  const onOpenReposter = React.useCallback(() => {
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#clickthroughReposter',
-      feedContext,
-    })
-  }, [sendInteraction, post, feedContext])
-
-  const onOpenEmbed = React.useCallback(() => {
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#clickthroughEmbed',
-      feedContext,
-    })
-  }, [sendInteraction, post, feedContext])
-
-  const onBeforePress = React.useCallback(() => {
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#clickthroughItem',
-      feedContext,
-    })
-    precacheProfile(queryClient, post.author)
-  }, [queryClient, post, sendInteraction, feedContext])
-
-  const outerStyles = [
-    styles.outer,
-    {
-      borderColor: pal.colors.border,
-      paddingBottom:
-        isThreadLastChild || (!isThreadChild && !isThreadParent)
-          ? 8
-          : undefined,
-      borderTopWidth:
-        hideTopBorder || isThreadChild ? 0 : StyleSheet.hairlineWidth,
-    },
-  ]
-
-  const {currentAccount} = useSession()
-  const isOwner =
-    AppBskyFeedDefs.isReasonRepost(reason) &&
-    reason.by.did === currentAccount?.did
-
-  /**
-   * If `post[0]` in this slice is the actual root post (not an orphan thread),
-   * then we may have a threadgate record to reference
-   */
-  const threadgateRecord = AppBskyFeedThreadgate.isRecord(
-    rootPost.threadgate?.record,
-  )
-    ? rootPost.threadgate.record
-    : undefined
-
-  const [hover, setHover] = useState(false)
-  return (
-    <Link
-      testID={`feedItem-by-${post.author.handle}`}
-      style={outerStyles}
-      href={href}
-      noFeedback
-      accessible={false}
-      onBeforePress={onBeforePress}
-      dataSet={{feedContext}}
-      onPointerEnter={() => {
-        setHover(true)
-      }}
-      onPointerLeave={() => {
-        setHover(false)
-      }}>
-      <SubtleWebHover hover={hover} />
-      <View style={{flexDirection: 'row', gap: 10, paddingLeft: 8}}>
-        <View style={{width: 42}}>
-          {isThreadChild && (
-            <View
-              style={[
-                styles.replyLine,
-                {
-                  flexGrow: 1,
-                  backgroundColor: pal.colors.replyLine,
-                  marginBottom: 4,
-                },
-              ]}
-            />
-          )}
-        </View>
-
-        <View style={{paddingTop: 12, flexShrink: 1}}>
-          {isReasonFeedSource(reason) ? (
-            <Link href={reason.href}>
-              <Text
-                type="sm-bold"
-                style={pal.textLight}
-                lineHeight={1.2}
-                numberOfLines={1}>
-                <Trans context="from-feed">
-                  From{' '}
-                  <FeedNameText
-                    type="sm-bold"
-                    uri={reason.uri}
-                    href={reason.href}
-                    lineHeight={1.2}
-                    numberOfLines={1}
-                    style={pal.textLight}
-                  />
-                </Trans>
-              </Text>
-            </Link>
-          ) : AppBskyFeedDefs.isReasonRepost(reason) ? (
-            <Link
-              style={styles.includeReason}
-              href={makeProfileLink(reason.by)}
-              title={
-                isOwner
-                  ? _(msg`Reposted by you`)
-                  : _(
-                      msg`Reposted by ${sanitizeDisplayName(
-                        reason.by.displayName || reason.by.handle,
-                      )}`,
-                    )
-              }
-              onBeforePress={onOpenReposter}>
-              <RepostIcon
-                style={{color: pal.colors.textLight, marginRight: 3}}
-                width={13}
-                height={13}
-              />
-              <Text
-                type="sm-bold"
-                style={pal.textLight}
-                lineHeight={1.2}
-                numberOfLines={1}>
-                {isOwner ? (
-                  <Trans>Reposted by you</Trans>
-                ) : (
-                  <Trans>
-                    Reposted by{' '}
-                    <ProfileHoverCard inline did={reason.by.did}>
-                      <TextLinkOnWebOnly
-                        type="sm-bold"
-                        style={pal.textLight}
-                        lineHeight={1.2}
-                        numberOfLines={1}
-                        text={
-                          <Text
-                            emoji
-                            type="sm-bold"
-                            style={pal.textLight}
-                            lineHeight={1.2}>
-                            {sanitizeDisplayName(
-                              reason.by.displayName ||
-                                sanitizeHandle(reason.by.handle),
-                              moderation.ui('displayName'),
-                            )}
-                          </Text>
-                        }
-                        href={makeProfileLink(reason.by)}
-                        onBeforePress={onOpenReposter}
-                      />
-                    </ProfileHoverCard>
-                  </Trans>
-                )}
-              </Text>
-            </Link>
-          ) : AppBskyFeedDefs.isReasonPin(reason) ? (
-            <View style={styles.includeReason}>
-              <PinIcon
-                style={{color: pal.colors.textLight, marginRight: 3}}
-                width={13}
-                height={13}
-              />
-              <Text
-                type="sm-bold"
-                style={pal.textLight}
-                lineHeight={1.2}
-                numberOfLines={1}>
-                <Trans>Pinned</Trans>
-              </Text>
-            </View>
-          ) : null}
-        </View>
-      </View>
-
-      <View style={styles.layout}>
-        <View style={styles.layoutAvi}>
-          <AviFollowButton author={post.author} moderation={moderation}>
-            <PreviewableUserAvatar
-              size={42}
-              profile={post.author}
-              moderation={moderation.ui('avatar')}
-              type={post.author.associated?.labeler ? 'labeler' : 'user'}
-              onBeforePress={onOpenAuthor}
-            />
-          </AviFollowButton>
-          {isThreadParent && (
-            <View
-              style={[
-                styles.replyLine,
-                {
-                  flexGrow: 1,
-                  backgroundColor: pal.colors.replyLine,
-                  marginTop: 4,
-                },
-              ]}
-            />
-          )}
-        </View>
-        <View style={styles.layoutContent}>
-          <PostMeta
-            author={post.author}
-            moderation={moderation}
-            timestamp={post.indexedAt}
-            postHref={href}
-            onOpenAuthor={onOpenAuthor}
-          />
-          {showReplyTo &&
-            (parentAuthor || isParentBlocked || isParentNotFound) && (
-              <ReplyToLabel
-                blocked={isParentBlocked}
-                notFound={isParentNotFound}
-                profile={parentAuthor}
-              />
-            )}
-          <LabelsOnMyPost post={post} />
-          <PostContent
-            moderation={moderation}
-            richText={richText}
-            postEmbed={post.embed}
-            postAuthor={post.author}
-            onOpenEmbed={onOpenEmbed}
-            post={post}
-            threadgateRecord={threadgateRecord}
-          />
-          <PostCtrls
-            post={post}
-            record={record}
-            richText={richText}
-            onPressReply={onPressReply}
-            logContext="FeedItem"
-            feedContext={feedContext}
-            threadgateRecord={threadgateRecord}
-          />
-        </View>
-      </View>
-    </Link>
-  )
-}
-FeedItemInner = memo(FeedItemInner)
-
-let PostContent = ({
-  post,
-  moderation,
-  richText,
-  postEmbed,
-  postAuthor,
-  onOpenEmbed,
-  threadgateRecord,
-}: {
-  moderation: ModerationDecision
-  richText: RichTextAPI
-  postEmbed: AppBskyFeedDefs.PostView['embed']
-  postAuthor: AppBskyFeedDefs.PostView['author']
-  onOpenEmbed: () => void
-  post: AppBskyFeedDefs.PostView
-  threadgateRecord?: AppBskyFeedThreadgate.Record
-}): React.ReactNode => {
-  const pal = usePalette('default')
-  const {_} = useLingui()
-  const {currentAccount} = useSession()
-  const [limitLines, setLimitLines] = useState(
-    () => countLines(richText.text) >= MAX_POST_LINES,
-  )
-  const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({
-    threadgateRecord,
-  })
-  const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => {
-    const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri)
-    const rootPostUri = AppBskyFeedPost.isRecord(post.record)
-      ? post.record?.reply?.root?.uri || post.uri
-      : undefined
-    const isControlledByViewer =
-      rootPostUri && new AtUri(rootPostUri).host === currentAccount?.did
-    return isControlledByViewer && isPostHiddenByThreadgate
-      ? [
-          {
-            type: 'reply-hidden',
-            source: {type: 'user', did: currentAccount?.did},
-            priority: 6,
-          },
-        ]
-      : []
-  }, [post, currentAccount?.did, threadgateHiddenReplies])
-
-  const onPressShowMore = React.useCallback(() => {
-    setLimitLines(false)
-  }, [setLimitLines])
-
-  return (
-    <ContentHider
-      testID="contentHider-post"
-      modui={moderation.ui('contentList')}
-      ignoreMute
-      childContainerStyle={styles.contentHiderChild}>
-      <PostAlerts
-        modui={moderation.ui('contentList')}
-        style={[a.py_2xs]}
-        additionalCauses={additionalPostAlerts}
-      />
-      {richText.text ? (
-        <View style={styles.postTextContainer}>
-          <RichText
-            enableTags
-            testID="postText"
-            value={richText}
-            numberOfLines={limitLines ? MAX_POST_LINES : undefined}
-            style={[a.flex_1, a.text_md]}
-            authorHandle={postAuthor.handle}
-          />
-        </View>
-      ) : undefined}
-      {limitLines ? (
-        <TextLink
-          text={_(msg`Show More`)}
-          style={pal.link}
-          onPress={onPressShowMore}
-          href="#"
-        />
-      ) : undefined}
-      {postEmbed ? (
-        <View style={[a.pb_xs]}>
-          <PostEmbeds
-            embed={postEmbed}
-            moderation={moderation}
-            onOpen={onOpenEmbed}
-            viewContext={PostEmbedViewContext.Feed}
-          />
-        </View>
-      ) : null}
-    </ContentHider>
-  )
-}
-PostContent = memo(PostContent)
-
-function ReplyToLabel({
-  profile,
-  blocked,
-  notFound,
-}: {
-  profile: AppBskyActorDefs.ProfileViewBasic | undefined
-  blocked?: boolean
-  notFound?: boolean
-}) {
-  const pal = usePalette('default')
-  const {currentAccount} = useSession()
-
-  let label
-  if (blocked) {
-    label = <Trans context="description">Reply to a blocked post</Trans>
-  } else if (notFound) {
-    label = <Trans context="description">Reply to a post</Trans>
-  } else if (profile != null) {
-    const isMe = profile.did === currentAccount?.did
-    if (isMe) {
-      label = <Trans context="description">Reply to you</Trans>
-    } else {
-      label = (
-        <Trans context="description">
-          Reply to{' '}
-          <ProfileHoverCard inline did={profile.did}>
-            <TextLinkOnWebOnly
-              type="md"
-              style={pal.textLight}
-              lineHeight={1.2}
-              numberOfLines={1}
-              href={makeProfileLink(profile)}
-              text={
-                <Text emoji type="md" style={pal.textLight} lineHeight={1.2}>
-                  {profile.displayName
-                    ? sanitizeDisplayName(profile.displayName)
-                    : sanitizeHandle(profile.handle)}
-                </Text>
-              }
-            />
-          </ProfileHoverCard>
-        </Trans>
-      )
-    }
-  }
-
-  if (!label) {
-    // Should not happen.
-    return null
-  }
-
-  return (
-    <View style={[s.flexRow, s.mb2, s.alignCenter]}>
-      <FontAwesomeIcon
-        icon="reply"
-        size={9}
-        style={[{color: pal.colors.textLight} as FontAwesomeIconStyle, s.mr5]}
-      />
-      <Text
-        type="md"
-        style={[pal.textLight, s.mr2]}
-        lineHeight={1.2}
-        numberOfLines={1}>
-        {label}
-      </Text>
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  outer: {
-    paddingLeft: 10,
-    paddingRight: 15,
-    // @ts-ignore web only -prf
-    cursor: 'pointer',
-  },
-  replyLine: {
-    width: 2,
-    marginLeft: 'auto',
-    marginRight: 'auto',
-  },
-  includeReason: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    marginTop: 2,
-    marginBottom: 2,
-    marginLeft: -16,
-  },
-  layout: {
-    flexDirection: 'row',
-    marginTop: 1,
-  },
-  layoutAvi: {
-    paddingLeft: 8,
-    paddingRight: 10,
-    position: 'relative',
-    zIndex: 999,
-  },
-  layoutContent: {
-    position: 'relative',
-    flex: 1,
-    zIndex: 0,
-  },
-  alert: {
-    marginTop: 6,
-    marginBottom: 6,
-  },
-  postTextContainer: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    flexWrap: 'wrap',
-    paddingBottom: 2,
-    overflow: 'hidden',
-  },
-  contentHiderChild: {
-    marginTop: 6,
-  },
-  embed: {
-    marginBottom: 6,
-  },
-  translateLink: {
-    marginBottom: 6,
-  },
-})