diff options
author | Eric Bailey <git@esb.lol> | 2024-08-21 21:20:45 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-21 19:20:45 -0700 |
commit | 6616a6467ec53aa71e5f823c2d8c46dc01442703 (patch) | |
tree | 5e49d6916bc9b9fc71a475cf0d02f169c744bf59 /src/view/com/post-thread | |
parent | 56ab5e177fa2b24d0e5d9d969aa37532b96128da (diff) | |
download | voidsky-6616a6467ec53aa71e5f823c2d8c46dc01442703.tar.zst |
Detached QPs and hidden replies (#4878)
Co-authored-by: Hailey <me@haileyok.com>
Diffstat (limited to 'src/view/com/post-thread')
-rw-r--r-- | src/view/com/post-thread/PostQuotes.tsx | 5 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 65 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 34 |
3 files changed, 91 insertions, 13 deletions
diff --git a/src/view/com/post-thread/PostQuotes.tsx b/src/view/com/post-thread/PostQuotes.tsx index d573d27a1..f91a041d7 100644 --- a/src/view/com/post-thread/PostQuotes.tsx +++ b/src/view/com/post-thread/PostQuotes.tsx @@ -10,7 +10,6 @@ import {useLingui} from '@lingui/react' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' -import {isWeb} from '#/platform/detection' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {usePostQuotesQuery} from '#/state/queries/post-quotes' import {useResolveUriQuery} from '#/state/queries/resolve-uri' @@ -25,16 +24,14 @@ import {List} from '../util/List' function renderItem({ item, - index, }: { item: { post: AppBskyFeedDefs.PostView moderation: ModerationDecision record: AppBskyFeedPost.Record } - index: number }) { - return <Post post={item.post} hideTopBorder={index === 0 && !isWeb} /> + return <Post post={item.post} /> } function keyExtractor(item: { diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index c64be8d67..bd778fd98 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -3,7 +3,12 @@ import {StyleSheet, useWindowDimensions, View} from 'react-native' import {runOnJS} from 'react-native-reanimated' import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {AppBskyFeedDefs} from '@atproto/api' +import { + AppBskyFeedDefs, + AppBskyFeedPost, + AppBskyFeedThreadgate, + AtUri, +} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -23,6 +28,7 @@ import { usePostThreadQuery, } from '#/state/queries/post-thread' import {usePreferencesQuery} from '#/state/queries/preferences' +import {useThreadgateRecordQuery} from '#/state/queries/threadgate' import {useSession} from '#/state/session' import {useComposerControls} from '#/state/shell' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' @@ -113,6 +119,28 @@ export function PostThread({uri}: {uri: string | undefined}) { ) const rootPost = thread?.type === 'post' ? thread.post : undefined const rootPostRecord = thread?.type === 'post' ? thread.record : undefined + const replyRef = + rootPostRecord && AppBskyFeedPost.isRecord(rootPostRecord) + ? rootPostRecord.reply + : undefined + const rootPostUri = replyRef ? replyRef.root.uri : rootPost?.uri + + const isOP = + currentAccount && + rootPostUri && + currentAccount?.did === new AtUri(rootPostUri).host + const {data: threadgateRecord} = useThreadgateRecordQuery({ + /** + * If the user is the OP and the root post has a threadgate, we should load + * the threadgate record. Otherwise, fallback to initialData, which is taken + * from the response from `getPostThread`. + */ + enabled: Boolean(isOP && rootPostUri), + postUri: rootPostUri, + initialData: rootPost?.threadgate?.record as + | AppBskyFeedThreadgate.Record + | undefined, + }) const moderationOpts = useModerationOpts() const isNoPwi = React.useMemo(() => { @@ -167,6 +195,9 @@ export function PostThread({uri}: {uri: string | undefined}) { const skeleton = React.useMemo(() => { const threadViewPrefs = preferences?.threadViewPrefs if (!threadViewPrefs || !thread) return null + const threadgateRecordHiddenReplies = new Set<string>( + threadgateRecord?.hiddenReplies || [], + ) return createThreadSkeleton( sortThread( @@ -175,11 +206,13 @@ export function PostThread({uri}: {uri: string | undefined}) { threadModerationCache, currentDid, justPostedUris, + threadgateRecordHiddenReplies, ), - !!currentDid, + currentDid, treeView, threadModerationCache, hiddenRepliesState !== HiddenRepliesState.Hide, + threadgateRecordHiddenReplies, ) }, [ thread, @@ -189,6 +222,7 @@ export function PostThread({uri}: {uri: string | undefined}) { threadModerationCache, hiddenRepliesState, justPostedUris, + threadgateRecord, ]) const error = React.useMemo(() => { @@ -425,6 +459,7 @@ export function PostThread({uri}: {uri: string | undefined}) { <PostThreadItem post={item.post} record={item.record} + threadgateRecord={threadgateRecord ?? undefined} moderation={threadModerationCache.get(item)} treeView={treeView} depth={item.ctx.depth} @@ -545,23 +580,25 @@ function isThreadBlocked(v: unknown): v is ThreadBlocked { function createThreadSkeleton( node: ThreadNode, - hasSession: boolean, + currentDid: string | undefined, treeView: boolean, modCache: ThreadModerationCache, showHiddenReplies: boolean, + threadgateRecordHiddenReplies: Set<string>, ): ThreadSkeletonParts | null { if (!node) return null return { - parents: Array.from(flattenThreadParents(node, hasSession)), + parents: Array.from(flattenThreadParents(node, !!currentDid)), highlightedPost: node, replies: Array.from( flattenThreadReplies( node, - hasSession, + currentDid, treeView, modCache, showHiddenReplies, + threadgateRecordHiddenReplies, ), ), } @@ -594,14 +631,15 @@ enum HiddenReplyType { function* flattenThreadReplies( node: ThreadNode, - hasSession: boolean, + currentDid: string | undefined, treeView: boolean, modCache: ThreadModerationCache, showHiddenReplies: boolean, + threadgateRecordHiddenReplies: Set<string>, ): Generator<YieldedItem, HiddenReplyType> { if (node.type === 'post') { // dont show pwi-opted-out posts to logged out users - if (!hasSession && hasPwiOptOut(node)) { + if (!currentDid && hasPwiOptOut(node)) { return HiddenReplyType.None } @@ -616,6 +654,16 @@ function* flattenThreadReplies( return HiddenReplyType.Hidden } } + + if (!showHiddenReplies) { + const hiddenByThreadgate = threadgateRecordHiddenReplies.has( + node.post.uri, + ) + const authorIsViewer = node.post.author.did === currentDid + if (hiddenByThreadgate && !authorIsViewer) { + return HiddenReplyType.Hidden + } + } } if (!node.ctx.isHighlightedPost) { @@ -627,10 +675,11 @@ function* flattenThreadReplies( for (const reply of node.replies) { let hiddenReply = yield* flattenThreadReplies( reply, - hasSession, + currentDid, treeView, modCache, showHiddenReplies, + threadgateRecordHiddenReplies, ) if (hiddenReply > hiddenReplies) { hiddenReplies = hiddenReply diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 26a5f2f03..da187f5d9 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -3,6 +3,7 @@ import {StyleSheet, View} from 'react-native' import { AppBskyFeedDefs, AppBskyFeedPost, + AppBskyFeedThreadgate, AtUri, ModerationDecision, RichText as RichTextAPI, @@ -29,6 +30,7 @@ import {isWeb} from 'platform/detection' import {useSession} from 'state/session' import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn' import {atoms as a} from '#/alf' +import {AppModerationCause} from '#/components/Pills' import {RichText} from '#/components/RichText' import {ContentHider} from '../../../components/moderation/ContentHider' import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' @@ -61,6 +63,7 @@ export function PostThreadItem({ overrideBlur, onPostReply, hideTopBorder, + threadgateRecord, }: { post: AppBskyFeedDefs.PostView record: AppBskyFeedPost.Record @@ -77,6 +80,7 @@ export function PostThreadItem({ overrideBlur: boolean onPostReply: (postUri: string | undefined) => void hideTopBorder?: boolean + threadgateRecord?: AppBskyFeedThreadgate.Record }) { const postShadowed = usePostShadow(post) const richText = useMemo( @@ -111,6 +115,7 @@ export function PostThreadItem({ overrideBlur={overrideBlur} onPostReply={onPostReply} hideTopBorder={hideTopBorder} + threadgateRecord={threadgateRecord} /> ) } @@ -154,6 +159,7 @@ let PostThreadItemLoaded = ({ overrideBlur, onPostReply, hideTopBorder, + threadgateRecord, }: { post: Shadow<AppBskyFeedDefs.PostView> record: AppBskyFeedPost.Record @@ -171,6 +177,7 @@ let PostThreadItemLoaded = ({ overrideBlur: boolean onPostReply: (postUri: string | undefined) => void hideTopBorder?: boolean + threadgateRecord?: AppBskyFeedThreadgate.Record }): React.ReactNode => { const pal = usePalette('default') const {_} = useLingui() @@ -199,6 +206,24 @@ let PostThreadItemLoaded = ({ return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') }, [post.uri, post.author]) const repostsTitle = _(msg`Reposts of this post`) + const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => { + const isPostHiddenByThreadgate = threadgateRecord?.hiddenReplies?.includes( + post.uri, + ) + const isControlledByViewer = + threadgateRecord && + new AtUri(threadgateRecord.post).host === currentAccount?.did + if (!isControlledByViewer) return [] + return threadgateRecord && isPostHiddenByThreadgate + ? [ + { + type: 'reply-hidden', + source: {type: 'user', did: new AtUri(threadgateRecord.post).host}, + priority: 6, + }, + ] + : [] + }, [post, threadgateRecord, currentAccount?.did]) const quotesHref = React.useMemo(() => { const urip = new AtUri(post.uri) return makeProfileLink(post.author, 'post', urip.rkey, 'quotes') @@ -320,6 +345,7 @@ let PostThreadItemLoaded = ({ size="lg" includeMute style={[a.pt_2xs, a.pb_sm]} + additionalCauses={additionalPostAlerts} /> {richText?.text ? ( <View @@ -420,6 +446,7 @@ let PostThreadItemLoaded = ({ onPressReply={onPressReply} onPostReply={onPostReply} logContext="PostThreadItem" + threadgateRecord={threadgateRecord} /> </View> </View> @@ -540,6 +567,7 @@ let PostThreadItemLoaded = ({ <PostAlerts modui={moderation.ui('contentList')} style={[a.pt_2xs, a.pb_2xs]} + additionalCauses={additionalPostAlerts} /> {richText?.text ? ( <View style={styles.postTextContainer}> @@ -571,6 +599,7 @@ let PostThreadItemLoaded = ({ richText={richText} onPressReply={onPressReply} logContext="PostThreadItem" + threadgateRecord={threadgateRecord} /> </View> </View> @@ -677,6 +706,7 @@ function ExpandedPostDetails({ const pal = usePalette('default') const {_} = useLingui() const openLink = useOpenLink() + const isRootPost = !('reply' in post.record) const onTranslatePress = React.useCallback(() => { openLink(translatorUrl) @@ -693,7 +723,9 @@ function ExpandedPostDetails({ s.mb10, ]}> <Text style={[a.text_sm, pal.textLight]}>{niceDate(post.indexedAt)}</Text> - <WhoCanReply post={post} isThreadAuthor={isThreadAuthor} /> + {isRootPost && ( + <WhoCanReply post={post} isThreadAuthor={isThreadAuthor} /> + )} {needsTranslation && ( <> <Text style={[a.text_sm, pal.textLight]}>·</Text> |