New reply tree layout

This commit is contained in:
Eric Bailey
2025-05-27 17:52:41 -05:00
parent 15aca24547
commit e5cc0c0ed2
6 changed files with 112 additions and 56 deletions
@@ -1,10 +1,10 @@
import {View} from 'react-native' import {View} from 'react-native'
import {msg, Plural,Trans} from '@lingui/macro' import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {type PostThreadParams,type Slice} from '#/state/queries/usePostThread' import {type PostThreadParams, type Slice} from '#/state/queries/usePostThread'
import {TREE_INDENT} from '#/screens/PostThread/const' import {TREE_AVI_WIDTH,TREE_INDENT} from '#/screens/PostThread/const'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {CirclePlus_Stroke2_Corner0_Rounded as CirclePlus} from '#/components/icons/CirclePlus' import {CirclePlus_Stroke2_Corner0_Rounded as CirclePlus} from '#/components/icons/CirclePlus'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
@@ -30,7 +30,8 @@ export function ReadMore({
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
{ {
borderRightWidth: 2, borderRightWidth: 2,
width: TREE_INDENT, width: TREE_INDENT + TREE_AVI_WIDTH / 2,
left: 1,
}, },
]} ]}
/> />
@@ -51,7 +52,9 @@ export function ReadMore({
style={[ style={[
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
{ {
marginLeft: isTreeView ? TREE_INDENT - 2 : TREE_INDENT, marginLeft: isTreeView
? TREE_INDENT + TREE_AVI_WIDTH / 2 - 1
: TREE_INDENT,
borderLeftWidth: 2, borderLeftWidth: 2,
borderBottomWidth: 2, borderBottomWidth: 2,
borderBottomLeftRadius: a.rounded_sm.borderRadius, borderBottomLeftRadius: a.rounded_sm.borderRadius,
@@ -27,7 +27,7 @@ import {TextLink} from '#/view/com/util/Link'
import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls' import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds' import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
import {PostMeta} from '#/view/com/util/PostMeta' import {PostMeta} from '#/view/com/util/PostMeta'
import {TREE_INDENT} from '#/screens/PostThread/const' import {TREE_AVI_WIDTH,TREE_INDENT} from '#/screens/PostThread/const'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {useInteractionState} from '#/components/hooks/useInteractionState' import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
@@ -39,6 +39,11 @@ import {RichText} from '#/components/RichText'
import {SubtleWebHover} from '#/components/SubtleWebHover' import {SubtleWebHover} from '#/components/SubtleWebHover'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
/**
* Mimic the space in PostMeta
*/
const TREE_AVI_PLUS_SPACE = TREE_AVI_WIDTH + a.gap_xs.gap
export function ThreadReply({ export function ThreadReply({
item, item,
overrides, overrides,
@@ -174,23 +179,24 @@ let PostThreadItemLoaded = ({
<View <View
style={[ style={[
a.flex_row, a.flex_row,
item.depth === 1 && [a.border_t, t.atoms.border_contrast_low], item.ui.indent === 1 && [a.border_t, t.atoms.border_contrast_low],
]}> ]}>
{Array.from(Array(item.depth - 1)).map((_, n: number) => ( {Array.from(Array(item.ui.indent - 1)).map((_, n: number) => (
<View <View
key={`${post.uri}-padding-${n}`} key={`${post.uri}-padding-${n}`}
style={[ style={[
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
{ {
borderRightWidth: 2, borderRightWidth: 2,
width: TREE_INDENT, width: TREE_INDENT + TREE_AVI_WIDTH / 2,
left: 1,
}, },
]} ]}
/> />
))} ))}
<View style={a.flex_1}> <View style={a.flex_1}>
<SubtleHover> <SubtleHover>
<View style={[a.px_lg, a.py_sm, item.depth === 1 && [a.pt_lg]]}> <View style={[a.px_lg, a.pt_sm, item.ui.indent === 1 && [a.pt_lg]]}>
<PostHider <PostHider
testID={`postThreadItem-by-${post.author.handle}`} testID={`postThreadItem-by-${post.author.handle}`}
href={postHref} href={postHref}
@@ -207,53 +213,74 @@ let PostThreadItemLoaded = ({
moderation={moderation} moderation={moderation}
timestamp={post.indexedAt} timestamp={post.indexedAt}
postHref={postHref} postHref={postHref}
avatarSize={24} avatarSize={TREE_AVI_WIDTH}
style={[a.pb_xs]} style={[a.pb_xs]}
showAvatar showAvatar
/> />
<LabelsOnMyPost post={post} style={[a.pb_xs]} /> <View style={[a.flex_row]}>
<PostAlerts <View style={[a.relative, {width: TREE_AVI_PLUS_SPACE}]}>
modui={moderation.ui('contentList')} {item.ui.showChildReplyLine && (
style={[a.pb_2xs]} <View
additionalCauses={additionalPostAlerts} style={[
/> a.h_full,
{richText?.text ? ( t.atoms.border_contrast_low,
<View style={[a.pb_2xs, a.pr_sm]}> {
<RichText borderRightWidth: 2,
enableTags width: '50%',
value={richText} left: -1,
style={[a.flex_1, a.text_md]} },
numberOfLines={limitLines ? MAX_POST_LINES : undefined} ]}
authorHandle={post.author.handle} />
shouldProxyLinks={true} )}
</View>
<View style={[a.flex_1]}>
<LabelsOnMyPost post={post} style={[a.pb_xs]} />
<PostAlerts
modui={moderation.ui('contentList')}
style={[a.pb_2xs]}
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
<View style={[a.pb_2xs, a.pr_sm]}>
<RichText
enableTags
value={richText}
style={[a.flex_1, a.text_md]}
numberOfLines={
limitLines ? MAX_POST_LINES : undefined
}
authorHandle={post.author.handle}
shouldProxyLinks={true}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text={_(msg`Show More`)}
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{post.embed && (
<View style={[a.pb_xs]}>
<PostEmbeds
embed={post.embed}
moderation={moderation}
viewContext={PostEmbedViewContext.Feed}
/>
</View>
)}
<PostCtrls
post={postShadow}
record={record}
richText={richText}
onPressReply={onPressReply}
logContext="PostThreadItem"
threadgateRecord={threadgateRecord}
/> />
</View> </View>
) : undefined} </View>
{limitLines ? (
<TextLink
text={_(msg`Show More`)}
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{post.embed && (
<View style={[a.pb_xs]}>
<PostEmbeds
embed={post.embed}
moderation={moderation}
viewContext={PostEmbedViewContext.Feed}
/>
</View>
)}
<PostCtrls
post={postShadow}
record={record}
richText={richText}
onPressReply={onPressReply}
logContext="PostThreadItem"
threadgateRecord={threadgateRecord}
/>
</View> </View>
</View> </View>
</PostHider> </PostHider>
+1
View File
@@ -1 +1,2 @@
export const TREE_INDENT = 16 export const TREE_INDENT = 16
export const TREE_AVI_WIDTH = 24
+6 -2
View File
@@ -114,8 +114,6 @@ export function flatten(
} }
} }
console.log(flattened)
/* /*
* Insert hidden items and buttons to show them * Insert hidden items and buttons to show them
*/ */
@@ -269,6 +267,9 @@ export function sort(
uri: item.uri, uri: item.uri,
depth: item.depth, depth: item.depth,
value: item.value, value: item.value,
parent: thread.find(
p => p.uri === item.value.post.record.reply.parent.uri,
),
oneUp, oneUp,
oneDown, oneDown,
moderationOpts, moderationOpts,
@@ -311,6 +312,9 @@ export function sort(
uri: child.uri, uri: child.uri,
depth: child.depth, depth: child.depth,
value: child.value, value: child.value,
parent: thread.find(
p => p.uri === child.value.post.record.reply.parent.uri,
),
oneUp: thread[ci - 1], oneUp: thread[ci - 1],
oneDown: thread[ci + 1], oneDown: thread[ci + 1],
moderationOpts, moderationOpts,
+2
View File
@@ -49,6 +49,8 @@ export type Slice =
isAnchor: boolean isAnchor: boolean
showParentReplyLine: boolean showParentReplyLine: boolean
showChildReplyLine: boolean showChildReplyLine: boolean
indent: number
parentHasBranchingReplies: boolean
} }
} }
| { | {
+21 -2
View File
@@ -65,17 +65,32 @@ export function threadPost({
uri, uri,
depth, depth,
value, value,
parent: up,
oneUp, oneUp,
oneDown,
moderationOpts, moderationOpts,
}: { }: {
uri: string uri: string
depth: number depth: number
value: $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost> value: $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost>
parent?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
oneUp?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number] oneUp?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
oneDown?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number] oneDown?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}): Extract<Slice, {type: 'threadPost'}> { }): Extract<Slice, {type: 'threadPost'}> {
const parent = (up && 'post' in up?.value
? up.value
: undefined) as unknown as AppBskyUnspeccedGetPostThreadV2.ThreadItemPost
const parentReplyCount = parent?.post?.replyCount || 0
const parentAdditionalReplies = parent?.moreReplies || 0
const parentHasBranchingReplies =
parentReplyCount > 1 && parentReplyCount - parentAdditionalReplies > 1
// if (!parentHasBranchingReplies) {
// console.log(value.post.record.text, {
// parentReplyCount,
// parentAdditionalReplies,
// })
// }
return { return {
type: 'threadPost', type: 'threadPost',
key: uri, key: uri,
@@ -96,6 +111,8 @@ export function threadPost({
isAnchor: depth === 0, isAnchor: depth === 0,
showParentReplyLine: !!oneUp && oneUp.depth !== 0 && oneUp.depth < depth, showParentReplyLine: !!oneUp && oneUp.depth !== 0 && oneUp.depth < depth,
showChildReplyLine: (value.post.replyCount || 0) > 0, showChildReplyLine: (value.post.replyCount || 0) > 0,
indent: parentHasBranchingReplies ? depth : up?.depth || depth,
parentHasBranchingReplies,
}, },
} }
} }
@@ -108,7 +125,9 @@ export function readMore({
return { return {
type: 'readMore' as const, type: 'readMore' as const,
key: `readMore:${parent.uri}`, key: `readMore:${parent.uri}`,
indent: parent.depth, indent: parent.ui.parentHasBranchingReplies
? parent.depth
: parent.depth - 1,
replyCount: parent.value.moreReplies, replyCount: parent.value.moreReplies,
nextAnchor: parent, nextAnchor: parent,
nextAnchorUri: new AtUri(parent.uri), nextAnchorUri: new AtUri(parent.uri),