WIP kill me

This commit is contained in:
Eric Bailey
2025-05-23 16:35:22 -05:00
parent 9e9ab55027
commit da4afb78d0
8 changed files with 147 additions and 43 deletions
@@ -0,0 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2Zm0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm0 3a1 1 0 0 1 1 1v3h3l.102.005a1 1 0 0 1 0 1.99L16 13h-3v3a1 1 0 1 1-2 0v-3H8a1 1 0 0 1 0-2h3V8a1 1 0 0 1 1-1Z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 344 B

+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const CirclePlus_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2Zm0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm0 3a1 1 0 0 1 1 1v3h3l.102.005a1 1 0 0 1 0 1.99L16 13h-3v3a1 1 0 1 1-2 0v-3H8a1 1 0 0 1 0-2h3V8a1 1 0 0 1 1-1Z',
})
+2 -1
View File
@@ -47,7 +47,8 @@ export interface ThreadCtx {
isHighlightedPost?: boolean isHighlightedPost?: boolean
hasMore?: boolean hasMore?: boolean
/** /**
* Means the loading state has parents * Means the loading state has parents, but once the data loads we don't even
* populate this value, so it's the same as `threadNode.parents.length`
*/ */
isParentLoading?: boolean isParentLoading?: boolean
/** /**
+4 -2
View File
@@ -41,11 +41,12 @@ export function usePostThread({
const query = useQuery({ const query = useQuery({
enabled, enabled,
queryKey, queryKey,
gcTime: 0,
async queryFn() { async queryFn() {
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({ const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
anchor: uri!, anchor: uri!,
branchingFactor: params.view === 'linear' ? 1 : 100, branchingFactor: params.view === 'linear' ? 1 : 3, // 100 TODO
below: 10, below: 3,
sorting: mapSortOptionsToSortID(params.sort), sorting: mapSortOptionsToSortID(params.sort),
}) })
return data return data
@@ -95,6 +96,7 @@ export function usePostThread({
return { return {
...query, ...query,
data: { data: {
anchorIndex: items.findIndex(i => Boolean(i.ui?.isAnchor)),
items, items,
threadgate: query.data?.threadgate, threadgate: query.data?.threadgate,
}, },
+50 -24
View File
@@ -1,4 +1,6 @@
import { import {
APP_BSKY_UNSPECCED,
AtUri,
AppBskyUnspeccedGetPostThreadV2, AppBskyUnspeccedGetPostThreadV2,
type ModerationDecision, type ModerationDecision,
type ModerationOpts, type ModerationOpts,
@@ -21,6 +23,54 @@ export function flatten(
) { ) {
const flattened: Slice[] = sorted.items const flattened: Slice[] = sorted.items
const unhydratedReplyIntervals = []
for (let i = 0; i < flattened.length; i++) {
const item = flattened[i]
if (item.type === 'threadPost') {
// TODO should not insert if not found post etc
if (item.ui.isAnchor && hasSession && !item.value.post.viewer?.replyDisabled) {
flattened.splice(i + 1, 0, {
type: 'replyComposer',
key: 'replyComposer',
})
}
const prev = unhydratedReplyIntervals[unhydratedReplyIntervals.length - 1]
if (item.annotations.has(APP_BSKY_UNSPECCED.GetPostThreadV2HasMoreReplies)) {
unhydratedReplyIntervals.push({
item,
replyCount: item.value.post.replyCount || 0,
})
}
/*
* If direct child of previous item with `hasMoreReplies`, subtract
*/
if (prev && item.depth === prev.item.depth + 1) {
prev.replyCount = Math.max(0, prev.replyCount - 1)
}
if (prev && item.depth <= prev.item.depth) {
flattened.splice(i, 0, {
type: 'readMore',
key: `readMore:${prev.item.uri}`,
indent: prev.item.depth + (item.depth < prev.item.depth ? -1 : 0),
replyCount: prev.replyCount,
nextAnchor: prev.item,
nextAnchorUri: new AtUri(prev.item.uri),
})
unhydratedReplyIntervals.pop()
}
}
}
/*
* Insert hidden items and buttons to show them
*/
if (sorted.hidden.length) { if (sorted.hidden.length) {
if (showHidden) { if (showHidden) {
flattened.push(...sorted.hidden) flattened.push(...sorted.hidden)
@@ -55,30 +105,6 @@ export function flatten(
} }
} }
if (hasSession) {
for (let i = 0; i < flattened.length; i++) {
const item = flattened[i]
// TODO should not insert if not found post etc
if (item.type === 'threadPost') {
if (item.ui.isAnchor) {
flattened.splice(i + 1, 0, {
type: 'replyComposer',
key: 'replyComposer',
})
}
if (
item.value.post.replyCount &&
item.value.post.replyCount > 0 &&
!item.ui.showChildReplyLine
) {
console.log('insert more link')
}
}
}
}
return flattened return flattened
} }
+6 -2
View File
@@ -1,6 +1,7 @@
import { import {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
type APP_BSKY_UNSPECCED, type APP_BSKY_UNSPECCED,
type AtUri,
type AppBskyFeedDefs, type AppBskyFeedDefs,
type AppBskyFeedPost, type AppBskyFeedPost,
type AppBskyUnspeccedGetPostThreadV2, type AppBskyUnspeccedGetPostThreadV2,
@@ -89,7 +90,10 @@ export type Slice =
kind: HiddenReplyKind kind: HiddenReplyKind
} }
| { | {
type: 'threadPostNoOp' type: 'readMore'
key: string key: string
comment: string indent: number
replyCount: number
nextAnchor: Extract<Slice, {type: 'threadPost'}>
nextAnchorUri: AtUri
} }
+7 -5
View File
@@ -90,7 +90,6 @@ type ThreadSkeletonParts = {
} }
const keyExtractor = (item: RowItem) => { const keyExtractor = (item: RowItem) => {
console.log(item._reactKey)
return item._reactKey return item._reactKey
} }
@@ -258,7 +257,6 @@ export function PostThread({uri}: {uri: string | undefined}) {
fetchedAt, fetchedAt,
randomCache, randomCache,
]) ])
console.log({thread, skeleton})
const error = React.useMemo(() => { const error = React.useMemo(() => {
if (AppBskyFeedDefs.isNotFoundPost(thread)) { if (AppBskyFeedDefs.isNotFoundPost(thread)) {
@@ -299,6 +297,9 @@ export function PostThread({uri}: {uri: string | undefined}) {
// maintainVisibleContentPosition and onContentSizeChange // maintainVisibleContentPosition and onContentSizeChange
// to "hold onto" the correct row instead of the first one. // to "hold onto" the correct row instead of the first one.
/*
* This is basically `!!parents.length`, see notes on `isParentLoading`
*/
if (!highlightedPost.ctx.isParentLoading && !deferParents) { if (!highlightedPost.ctx.isParentLoading && !deferParents) {
// When progressively revealing parents, rendering a placeholder // When progressively revealing parents, rendering a placeholder
// here will cause scrolling jumps. Don't add it unless you test it. // here will cause scrolling jumps. Don't add it unless you test it.
@@ -325,6 +326,8 @@ export function PostThread({uri}: {uri: string | undefined}) {
return arr return arr
}, [skeleton, deferParents, maxParents, maxReplies]) }, [skeleton, deferParents, maxParents, maxReplies])
console.log({anchorIndex: posts.findIndex(p => p.ctx?.isHighlightedPost)})
// This is only used on the web to keep the post in view when its parents load. // This is only used on the web to keep the post in view when its parents load.
// On native, we rely on `maintainVisibleContentPosition` instead. // On native, we rely on `maintainVisibleContentPosition` instead.
const didAdjustScrollWeb = useRef<boolean>(false) const didAdjustScrollWeb = useRef<boolean>(false)
@@ -340,11 +343,8 @@ export function PostThread({uri}: {uri: string | undefined}) {
const headerNode = headerRef.current const headerNode = headerRef.current
if (postNode && headerNode) { if (postNode && headerNode) {
let pageY = (postNode as any as Element).getBoundingClientRect().top let pageY = (postNode as any as Element).getBoundingClientRect().top
console.log({pageY})
pageY -= (headerNode as any as Element).getBoundingClientRect().height pageY -= (headerNode as any as Element).getBoundingClientRect().height
console.log({pageY})
pageY = Math.max(0, pageY) pageY = Math.max(0, pageY)
console.log({pageY})
ref.current?.scrollToOffset({ ref.current?.scrollToOffset({
animated: false, animated: false,
offset: pageY, offset: pageY,
@@ -423,6 +423,8 @@ export function PostThread({uri}: {uri: string | undefined}) {
(skeleton.highlightedPost.ctx.isParentLoading || (skeleton.highlightedPost.ctx.isParentLoading ||
Boolean(skeleton?.parents && skeleton.parents.length > 0)) Boolean(skeleton?.parents && skeleton.parents.length > 0))
console.log({hasParents})
const renderItem = ({item, index}: {item: RowItem; index: number}) => { const renderItem = ({item, index}: {item: RowItem; index: number}) => {
if (item === REPLY_PROMPT && hasSession) { if (item === REPLY_PROMPT && hasSession) {
return ( return (
+72 -9
View File
@@ -13,9 +13,7 @@ import {cleanError} from '#/lib/strings/errors'
import {makeRecordUri} from '#/lib/strings/url-helpers' import {makeRecordUri} from '#/lib/strings/url-helpers'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
{ import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread'
/* import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread' */
}
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {ScrollProvider} from '#/lib/ScrollContext' import {ScrollProvider} from '#/lib/ScrollContext'
@@ -32,7 +30,10 @@ import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShow
import {List, type ListMethods} from '#/view/com/util/List' import {List, type ListMethods} from '#/view/com/util/List'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
import {Link} from '#/components/Link'
import {makeProfileLink} from '#/lib/routes/links'
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider' import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
import {CirclePlus_Stroke2_Corner0_Rounded as CirclePlus} from '#/components/icons/CirclePlus'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {ListFooter} from '#/components/Lists' import {ListFooter} from '#/components/Lists'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
@@ -41,7 +42,7 @@ import {Text} from '#/components/Typography'
const MAINTAIN_VISIBLE_CONTENT_POSITION = { const MAINTAIN_VISIBLE_CONTENT_POSITION = {
// We don't insert any elements before the root row while loading. // We don't insert any elements before the root row while loading.
// So the row we want to use as the scroll anchor is the first row. // So the row we want to use as the scroll anchor is the first row.
minIndexForVisible: 1, minIndexForVisible: 0,
} }
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
@@ -232,13 +233,11 @@ export function Inner({uri}: {uri: string | undefined}) {
// prevPost={prev} // prevPost={prev}
// nextPost={next} // nextPost={next}
isHighlightedPost={item.ui.isAnchor} isHighlightedPost={item.ui.isAnchor}
// @ts-expect-error hasMore={false} // TODO need to replace this entirely
hasMore={item.value.hasUnhydratedReplies}
showChildReplyLine={item.ui.showChildReplyLine} showChildReplyLine={item.ui.showChildReplyLine}
showParentReplyLine={item.ui.showParentReplyLine} showParentReplyLine={item.ui.showParentReplyLine}
hasPrecedingItem={ hasPrecedingItem={
// @ts-expect-error item.ui.showParentReplyLine
item.ui.showParentReplyLine || !!item.value.hasUnhydratedParents
} // !!hasUnrevealedParents // TODO } // !!hasUnrevealedParents // TODO
overrideBlur={ overrideBlur={
shownHiddenReplyKinds.has(HiddenReplyKind.Muted) && item.depth > 0 shownHiddenReplyKinds.has(HiddenReplyKind.Muted) && item.depth > 0
@@ -248,6 +247,68 @@ export function Inner({uri}: {uri: string | undefined}) {
/> />
</View> </View>
) )
} else if (item.type === 'readMore') {
return (
<View style={[a.flex_row, a.px_sm]}>
{Array.from(Array(item.indent - 1)).map((_, n: number) => (
<View
key={`${item.key}-padding-${n}`}
style={[
a.ml_sm,
t.atoms.border_contrast_low,
{
borderLeftWidth: 2,
paddingLeft: a.pl_sm.paddingLeft - 2, // minus border
},
]}
/>
))}
<View style={[a.ml_sm]}>
<View
style={[
t.atoms.border_contrast_low,
{
borderLeftWidth: 2,
borderBottomWidth: 2,
borderBottomLeftRadius: a.rounded_sm.borderRadius,
height: 12,
width: a.pl_sm.paddingLeft * 2,
},
]}
/>
</View>
<Link
label={_(msg`Read more replies`)}
to={makeProfileLink(
{
did: item.nextAnchorUri.host,
handle: item.nextAnchor.value.post.author.handle,
},
'post',
item.nextAnchorUri.rkey,
)}
style={[a.pt_2xs, a.pb_sm, a.gap_xs]}>
{({hovered, pressed}) => {
return (
<>
<CirclePlus
fill={t.atoms.text_contrast_high.color}
width={18}
/>
<Text
style={[
a.text_sm,
t.atoms.text_contrast_medium,
(hovered || pressed) && a.underline,
]}>
Read {item.replyCount} more replies
</Text>
</>
)
}}
</Link>
</View>
)
} else if (item.type === 'threadPostBlocked') { } else if (item.type === 'threadPostBlocked') {
return ( return (
<View <View
@@ -297,6 +358,8 @@ export function Inner({uri}: {uri: string | undefined}) {
return null return null
} }
console.log('PostThreadScreen', data?.anchorIndex)
return ( return (
<> <>
<Layout.Header.Outer headerRef={headerRef}> <Layout.Header.Outer headerRef={headerRef}>
@@ -337,7 +400,7 @@ export function Inner({uri}: {uri: string | undefined}) {
*/ */
maintainVisibleContentPosition={ maintainVisibleContentPosition={
isNative // && hasParents // TODO not sure we need this isNative // && hasParents // TODO not sure we need this
? MAINTAIN_VISIBLE_CONTENT_POSITION ? { minIndexForVisible: 0 } // MAINTAIN_VISIBLE_CONTENT_POSITION
: undefined : undefined
} }
desktopFixedHeight desktopFixedHeight