diff --git a/assets/icons/circlePlus_stroke2_corner0_rounded.svg b/assets/icons/circlePlus_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..1feeb3c3ed
--- /dev/null
+++ b/assets/icons/circlePlus_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/icons/CirclePlus.tsx b/src/components/icons/CirclePlus.tsx
new file mode 100644
index 0000000000..690e77326e
--- /dev/null
+++ b/src/components/icons/CirclePlus.tsx
@@ -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',
+})
diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts
index 4a75f85a79..7415283dc3 100644
--- a/src/state/queries/post-thread.ts
+++ b/src/state/queries/post-thread.ts
@@ -47,7 +47,8 @@ export interface ThreadCtx {
isHighlightedPost?: 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
/**
diff --git a/src/state/queries/usePostThread/index.ts b/src/state/queries/usePostThread/index.ts
index d0a265398c..f45d60bf6c 100644
--- a/src/state/queries/usePostThread/index.ts
+++ b/src/state/queries/usePostThread/index.ts
@@ -41,11 +41,12 @@ export function usePostThread({
const query = useQuery({
enabled,
queryKey,
+ gcTime: 0,
async queryFn() {
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
anchor: uri!,
- branchingFactor: params.view === 'linear' ? 1 : 100,
- below: 10,
+ branchingFactor: params.view === 'linear' ? 1 : 3, // 100 TODO
+ below: 3,
sorting: mapSortOptionsToSortID(params.sort),
})
return data
@@ -95,6 +96,7 @@ export function usePostThread({
return {
...query,
data: {
+ anchorIndex: items.findIndex(i => Boolean(i.ui?.isAnchor)),
items,
threadgate: query.data?.threadgate,
},
diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts
index 4da1935ba1..fa2a138386 100644
--- a/src/state/queries/usePostThread/traversal.ts
+++ b/src/state/queries/usePostThread/traversal.ts
@@ -1,4 +1,6 @@
import {
+ APP_BSKY_UNSPECCED,
+ AtUri,
AppBskyUnspeccedGetPostThreadV2,
type ModerationDecision,
type ModerationOpts,
@@ -21,6 +23,54 @@ export function flatten(
) {
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 (showHidden) {
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
}
diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts
index d3adffeb9b..4a7f36ff20 100644
--- a/src/state/queries/usePostThread/types.ts
+++ b/src/state/queries/usePostThread/types.ts
@@ -1,6 +1,7 @@
import {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type APP_BSKY_UNSPECCED,
+ type AtUri,
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyUnspeccedGetPostThreadV2,
@@ -89,7 +90,10 @@ export type Slice =
kind: HiddenReplyKind
}
| {
- type: 'threadPostNoOp'
+ type: 'readMore'
key: string
- comment: string
+ indent: number
+ replyCount: number
+ nextAnchor: Extract
+ nextAnchorUri: AtUri
}
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 69c72ebb38..1d22893340 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -90,7 +90,6 @@ type ThreadSkeletonParts = {
}
const keyExtractor = (item: RowItem) => {
- console.log(item._reactKey)
return item._reactKey
}
@@ -258,7 +257,6 @@ export function PostThread({uri}: {uri: string | undefined}) {
fetchedAt,
randomCache,
])
- console.log({thread, skeleton})
const error = React.useMemo(() => {
if (AppBskyFeedDefs.isNotFoundPost(thread)) {
@@ -299,6 +297,9 @@ export function PostThread({uri}: {uri: string | undefined}) {
// maintainVisibleContentPosition and onContentSizeChange
// 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) {
// When progressively revealing parents, rendering a placeholder
// 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
}, [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.
// On native, we rely on `maintainVisibleContentPosition` instead.
const didAdjustScrollWeb = useRef(false)
@@ -340,11 +343,8 @@ export function PostThread({uri}: {uri: string | undefined}) {
const headerNode = headerRef.current
if (postNode && headerNode) {
let pageY = (postNode as any as Element).getBoundingClientRect().top
- console.log({pageY})
pageY -= (headerNode as any as Element).getBoundingClientRect().height
- console.log({pageY})
pageY = Math.max(0, pageY)
- console.log({pageY})
ref.current?.scrollToOffset({
animated: false,
offset: pageY,
@@ -423,6 +423,8 @@ export function PostThread({uri}: {uri: string | undefined}) {
(skeleton.highlightedPost.ctx.isParentLoading ||
Boolean(skeleton?.parents && skeleton.parents.length > 0))
+ console.log({hasParents})
+
const renderItem = ({item, index}: {item: RowItem; index: number}) => {
if (item === REPLY_PROMPT && hasSession) {
return (
diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx
index 7db047b7af..68001a3ce3 100644
--- a/src/view/screens/PostThread.tsx
+++ b/src/view/screens/PostThread.tsx
@@ -13,9 +13,7 @@ import {cleanError} from '#/lib/strings/errors'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {isNative} from '#/platform/detection'
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 {useOpenComposer} from '#/lib/hooks/useOpenComposer'
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 {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
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 {CirclePlus_Stroke2_Corner0_Rounded as CirclePlus} from '#/components/icons/CirclePlus'
import * as Layout from '#/components/Layout'
import {ListFooter} from '#/components/Lists'
import * as Menu from '#/components/Menu'
@@ -41,7 +42,7 @@ import {Text} from '#/components/Typography'
const MAINTAIN_VISIBLE_CONTENT_POSITION = {
// 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.
- minIndexForVisible: 1,
+ minIndexForVisible: 0,
}
type Props = NativeStackScreenProps
@@ -232,13 +233,11 @@ export function Inner({uri}: {uri: string | undefined}) {
// prevPost={prev}
// nextPost={next}
isHighlightedPost={item.ui.isAnchor}
- // @ts-expect-error
- hasMore={item.value.hasUnhydratedReplies}
+ hasMore={false} // TODO need to replace this entirely
showChildReplyLine={item.ui.showChildReplyLine}
showParentReplyLine={item.ui.showParentReplyLine}
hasPrecedingItem={
- // @ts-expect-error
- item.ui.showParentReplyLine || !!item.value.hasUnhydratedParents
+ item.ui.showParentReplyLine
} // !!hasUnrevealedParents // TODO
overrideBlur={
shownHiddenReplyKinds.has(HiddenReplyKind.Muted) && item.depth > 0
@@ -248,6 +247,68 @@ export function Inner({uri}: {uri: string | undefined}) {
/>
)
+ } else if (item.type === 'readMore') {
+ return (
+
+ {Array.from(Array(item.indent - 1)).map((_, n: number) => (
+
+ ))}
+
+
+
+
+ {({hovered, pressed}) => {
+ return (
+ <>
+
+
+ Read {item.replyCount} more replies
+
+ >
+ )
+ }}
+
+
+ )
} else if (item.type === 'threadPostBlocked') {
return (
@@ -337,7 +400,7 @@ export function Inner({uri}: {uri: string | undefined}) {
*/
maintainVisibleContentPosition={
isNative // && hasParents // TODO not sure we need this
- ? MAINTAIN_VISIBLE_CONTENT_POSITION
+ ? { minIndexForVisible: 0 } // MAINTAIN_VISIBLE_CONTENT_POSITION
: undefined
}
desktopFixedHeight