Make optimistic deleted state not look like crap in tree view
This commit is contained in:
@@ -66,7 +66,7 @@ export function ThreadReply({
|
|||||||
const postShadow = usePostShadow(item.value.post)
|
const postShadow = usePostShadow(item.value.post)
|
||||||
|
|
||||||
if (postShadow === POST_TOMBSTONE) {
|
if (postShadow === POST_TOMBSTONE) {
|
||||||
return <PostThreadItemDeleted />
|
return <ThreadItemTreeReplyDeleted item={item} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -82,26 +82,157 @@ export function ThreadReply({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PostThreadItemDeleted({hideTopBorder}: {hideTopBorder?: boolean}) {
|
function ThreadItemTreeReplyDeleted({
|
||||||
|
item,
|
||||||
|
}: {
|
||||||
|
item: Extract<ThreadItem, {type: 'threadPost'}>
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<ThreadItemTreeReplyOuter item={item}>
|
||||||
|
<ThreadItemTreeReplyInner item={item}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.rounded_sm,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{
|
||||||
|
gap: 6,
|
||||||
|
paddingHorizontal: OUTER_SPACE / 2,
|
||||||
|
height: TREE_AVI_WIDTH,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<TrashIcon style={[t.atoms.text]} width={14} />
|
||||||
|
<Text style={[t.atoms.text_contrast_medium, a.mt_2xs]}>
|
||||||
|
<Trans>This post has been deleted.</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{item.ui.isLastChild && !item.ui.precedesChildReadMore && (
|
||||||
|
<View style={{height: OUTER_SPACE / 2}} />
|
||||||
|
)}
|
||||||
|
</ThreadItemTreeReplyInner>
|
||||||
|
</ThreadItemTreeReplyOuter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThreadItemTreeReplyOuter = memo(function ThreadItemTreeReplyOuter({
|
||||||
|
item,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
item: Extract<ThreadItem, {type: 'threadPost'}>
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const indents = Math.max(0, item.ui.indent - 1)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
item.ui.indent === 1 &&
|
||||||
|
!item.ui.showParentReplyLine && [
|
||||||
|
a.border_t,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
],
|
||||||
|
]}>
|
||||||
|
{Array.from(Array(indents)).map((_, n: number) => {
|
||||||
|
const isSkipped = item.ui.skippedIndentIndices.has(n)
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={`${item.value.post.uri}-padding-${n}`}
|
||||||
|
style={[
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
{
|
||||||
|
borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
|
||||||
|
width: TREE_INDENT + TREE_AVI_WIDTH / 2,
|
||||||
|
left: 1,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ThreadItemTreeReplyInner = memo(function ThreadItemTreeReplyInner({
|
||||||
|
item,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
item: Extract<ThreadItem, {type: 'threadPost'}>
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
t.atoms.bg,
|
a.flex_1, // TODO check on ios
|
||||||
t.atoms.border_contrast_low,
|
{
|
||||||
a.p_xl,
|
paddingHorizontal: OUTER_SPACE,
|
||||||
a.pl_lg,
|
paddingTop: OUTER_SPACE / 2,
|
||||||
a.flex_row,
|
},
|
||||||
a.gap_md,
|
item.ui.indent === 1 && [
|
||||||
!hideTopBorder && a.border_t,
|
!item.ui.showParentReplyLine && a.pt_lg,
|
||||||
|
!item.ui.showChildReplyLine && a.pb_sm,
|
||||||
|
],
|
||||||
|
item.ui.isLastChild &&
|
||||||
|
!item.ui.precedesChildReadMore && [
|
||||||
|
{
|
||||||
|
paddingBottom: OUTER_SPACE / 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
]}>
|
]}>
|
||||||
<TrashIcon style={[t.atoms.text]} />
|
{item.ui.indent > 1 && (
|
||||||
<Text style={[t.atoms.text_contrast_medium, a.mt_2xs]}>
|
<View
|
||||||
<Trans>This post has been deleted.</Trans>
|
style={[
|
||||||
</Text>
|
a.absolute,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
{
|
||||||
|
left: -1,
|
||||||
|
top: 0,
|
||||||
|
height:
|
||||||
|
TREE_AVI_WIDTH / 2 + REPLY_LINE_WIDTH / 2 + OUTER_SPACE / 2,
|
||||||
|
width: OUTER_SPACE,
|
||||||
|
borderLeftWidth: REPLY_LINE_WIDTH,
|
||||||
|
borderBottomWidth: REPLY_LINE_WIDTH,
|
||||||
|
borderBottomLeftRadius: a.rounded_sm.borderRadius,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
|
const ThreadItemTreeReplyChildReplyLine = memo(
|
||||||
|
function ThreadItemTreeReplyChildReplyLine({
|
||||||
|
item,
|
||||||
|
}: {
|
||||||
|
item: Extract<ThreadItem, {type: 'threadPost'}>
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View style={[a.relative, {width: TREE_AVI_PLUS_SPACE}]}>
|
||||||
|
{item.ui.showChildReplyLine && (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
{
|
||||||
|
borderRightWidth: 2,
|
||||||
|
width: '50%',
|
||||||
|
left: -1,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const ThreadReplyInner = memo(function ThreadReplyInner({
|
const ThreadReplyInner = memo(function ThreadReplyInner({
|
||||||
item,
|
item,
|
||||||
@@ -119,7 +250,6 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
|
|||||||
onPostSuccess?: (data: OnPostSuccessData) => void
|
onPostSuccess?: (data: OnPostSuccessData) => void
|
||||||
threadgateRecord?: AppBskyFeedThreadgate.Record
|
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||||
}): React.ReactNode {
|
}): React.ReactNode {
|
||||||
const t = useTheme()
|
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {openComposer} = useOpenComposer()
|
const {openComposer} = useOpenComposer()
|
||||||
@@ -180,160 +310,86 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
|
|||||||
setLimitLines(false)
|
setLimitLines(false)
|
||||||
}, [setLimitLines])
|
}, [setLimitLines])
|
||||||
|
|
||||||
const indents = Math.max(0, item.ui.indent - 1)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<ThreadItemTreeReplyOuter item={item}>
|
||||||
style={[
|
<SubtleHover>
|
||||||
a.flex_row,
|
<PostHider
|
||||||
item.ui.indent === 1 &&
|
testID={`postThreadItem-by-${post.author.handle}`}
|
||||||
!item.ui.showParentReplyLine && [
|
href={postHref}
|
||||||
a.border_t,
|
disabled={overrides?.moderation === true}
|
||||||
t.atoms.border_contrast_low,
|
modui={moderation.ui('contentList')}
|
||||||
],
|
iconSize={42}
|
||||||
]}>
|
iconStyles={{marginLeft: 2, marginRight: 2}}
|
||||||
{Array.from(Array(indents)).map((_, n: number) => {
|
profile={post.author}
|
||||||
const isSkipped = item.ui.skippedIndentIndices.has(n)
|
interpretFilterAsBlur>
|
||||||
return (
|
<ThreadItemTreeReplyInner item={item}>
|
||||||
<View
|
<View style={[a.flex_row, a.gap_md]}>
|
||||||
key={`${post.uri}-padding-${n}`}
|
<View style={[a.flex_1]}>
|
||||||
style={[
|
<PostMeta
|
||||||
t.atoms.border_contrast_low,
|
author={post.author}
|
||||||
{
|
moderation={moderation}
|
||||||
borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
|
timestamp={post.indexedAt}
|
||||||
width: TREE_INDENT + TREE_AVI_WIDTH / 2,
|
postHref={postHref}
|
||||||
left: 1,
|
avatarSize={TREE_AVI_WIDTH}
|
||||||
},
|
style={[a.pb_2xs]}
|
||||||
]}
|
showAvatar
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
<View style={a.flex_1}>
|
|
||||||
<SubtleHover>
|
|
||||||
<PostHider
|
|
||||||
testID={`postThreadItem-by-${post.author.handle}`}
|
|
||||||
href={postHref}
|
|
||||||
disabled={overrides?.moderation === true}
|
|
||||||
modui={moderation.ui('contentList')}
|
|
||||||
iconSize={42}
|
|
||||||
iconStyles={{marginLeft: 2, marginRight: 2}}
|
|
||||||
profile={post.author}
|
|
||||||
interpretFilterAsBlur>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
paddingHorizontal: OUTER_SPACE,
|
|
||||||
paddingTop: OUTER_SPACE / 2,
|
|
||||||
},
|
|
||||||
item.ui.indent === 1 && [
|
|
||||||
!item.ui.showParentReplyLine && a.pt_lg,
|
|
||||||
!item.ui.showChildReplyLine && a.pb_sm,
|
|
||||||
],
|
|
||||||
item.ui.isLastChild &&
|
|
||||||
!item.ui.precedesChildReadMore && [a.pb_sm],
|
|
||||||
]}>
|
|
||||||
{item.ui.indent > 1 && (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.absolute,
|
|
||||||
t.atoms.border_contrast_low,
|
|
||||||
{
|
|
||||||
left: -1,
|
|
||||||
top: 0,
|
|
||||||
height:
|
|
||||||
TREE_AVI_WIDTH / 2 +
|
|
||||||
REPLY_LINE_WIDTH / 2 +
|
|
||||||
OUTER_SPACE / 2,
|
|
||||||
width: OUTER_SPACE,
|
|
||||||
borderLeftWidth: REPLY_LINE_WIDTH,
|
|
||||||
borderBottomWidth: REPLY_LINE_WIDTH,
|
|
||||||
borderBottomLeftRadius: a.rounded_sm.borderRadius,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<View style={[a.flex_row]}>
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
<ThreadItemTreeReplyChildReplyLine item={item} />
|
||||||
<View style={[a.flex_1]}>
|
<View style={[a.flex_1]}>
|
||||||
<PostMeta
|
<LabelsOnMyPost post={post} style={[a.pb_2xs]} />
|
||||||
author={post.author}
|
<PostAlerts
|
||||||
moderation={moderation}
|
modui={moderation.ui('contentList')}
|
||||||
timestamp={post.indexedAt}
|
style={[a.pb_2xs]}
|
||||||
postHref={postHref}
|
additionalCauses={additionalPostAlerts}
|
||||||
avatarSize={TREE_AVI_WIDTH}
|
/>
|
||||||
style={[a.pb_2xs]}
|
{richText?.text ? (
|
||||||
showAvatar
|
<View>
|
||||||
/>
|
<RichText
|
||||||
<View style={[a.flex_row]}>
|
enableTags
|
||||||
<View style={[a.relative, {width: TREE_AVI_PLUS_SPACE}]}>
|
value={richText}
|
||||||
{item.ui.showChildReplyLine && (
|
style={[a.flex_1, a.text_md]}
|
||||||
<View
|
numberOfLines={
|
||||||
style={[
|
limitLines ? MAX_POST_LINES : undefined
|
||||||
a.flex_1,
|
}
|
||||||
t.atoms.border_contrast_low,
|
authorHandle={post.author.handle}
|
||||||
{
|
shouldProxyLinks={true}
|
||||||
borderRightWidth: 2,
|
|
||||||
width: '50%',
|
|
||||||
left: -1,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</View>
|
||||||
</View>
|
) : undefined}
|
||||||
<View style={[a.flex_1]}>
|
{limitLines ? (
|
||||||
<LabelsOnMyPost post={post} style={[a.pb_2xs]} />
|
<TextLink
|
||||||
<PostAlerts
|
text={_(msg`Show More`)}
|
||||||
modui={moderation.ui('contentList')}
|
style={pal.link}
|
||||||
style={[a.pb_2xs]}
|
onPress={onPressShowMore}
|
||||||
additionalCauses={additionalPostAlerts}
|
href="#"
|
||||||
/>
|
/>
|
||||||
{richText?.text ? (
|
) : undefined}
|
||||||
<View>
|
{post.embed && (
|
||||||
<RichText
|
<View style={[a.pb_xs]}>
|
||||||
enableTags
|
<PostEmbeds
|
||||||
value={richText}
|
embed={post.embed}
|
||||||
style={[a.flex_1, a.text_md]}
|
moderation={moderation}
|
||||||
numberOfLines={
|
viewContext={PostEmbedViewContext.Feed}
|
||||||
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}
|
</View>
|
||||||
{post.embed && (
|
)}
|
||||||
<View style={[a.pb_xs]}>
|
<PostControls
|
||||||
<PostEmbeds
|
post={postShadow}
|
||||||
embed={post.embed}
|
record={record}
|
||||||
moderation={moderation}
|
richText={richText}
|
||||||
viewContext={PostEmbedViewContext.Feed}
|
onPressReply={onPressReply}
|
||||||
/>
|
logContext="PostThreadItem"
|
||||||
</View>
|
threadgateRecord={threadgateRecord}
|
||||||
)}
|
/>
|
||||||
<PostControls
|
|
||||||
post={postShadow}
|
|
||||||
record={record}
|
|
||||||
richText={richText}
|
|
||||||
onPressReply={onPressReply}
|
|
||||||
logContext="PostThreadItem"
|
|
||||||
threadgateRecord={threadgateRecord}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</PostHider>
|
</ThreadItemTreeReplyInner>
|
||||||
</SubtleHover>
|
</PostHider>
|
||||||
</View>
|
</SubtleHover>
|
||||||
</View>
|
</ThreadItemTreeReplyOuter>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -347,7 +403,7 @@ function SubtleHover({children}: {children: React.ReactNode}) {
|
|||||||
<View
|
<View
|
||||||
onPointerEnter={onHoverIn}
|
onPointerEnter={onHoverIn}
|
||||||
onPointerLeave={onHoverOut}
|
onPointerLeave={onHoverOut}
|
||||||
style={a.pointer}>
|
style={[a.flex_1, a.pointer]}>
|
||||||
<SubtleWebHover hover={hover} />
|
<SubtleWebHover hover={hover} />
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user