about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/state/queries/post-thread.ts53
-rw-r--r--src/view/com/post-thread/PostThread.tsx4
-rw-r--r--src/view/com/post-thread/PostThreadLoadMore.tsx57
3 files changed, 112 insertions, 2 deletions
diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts
index f7d21a427..727eff253 100644
--- a/src/state/queries/post-thread.ts
+++ b/src/state/queries/post-thread.ts
@@ -41,6 +41,8 @@ export interface ThreadCtx {
   hasMore?: boolean
   isParentLoading?: boolean
   isChildLoading?: boolean
+  isSelfThread?: boolean
+  hasMoreSelfThread?: boolean
 }
 
 export type ThreadPost = {
@@ -88,9 +90,12 @@ export function usePostThreadQuery(uri: string | undefined) {
     gcTime: 0,
     queryKey: RQKEY(uri || ''),
     async queryFn() {
-      const res = await agent.getPostThread({uri: uri!})
+      const res = await agent.getPostThread({uri: uri!, depth: 10})
       if (res.success) {
-        return responseToThreadNodes(res.data.thread)
+        const thread = responseToThreadNodes(res.data.thread)
+        annotateSelfThread(thread)
+        console.log(thread)
+        return thread
       }
       return {type: 'unknown', uri: uri!}
     },
@@ -234,6 +239,8 @@ function responseToThreadNodes(
         isHighlightedPost: depth === 0,
         hasMore:
           direction === 'down' && !node.replies?.length && !!node.replyCount,
+        isSelfThread: false, // populated `annotateSelfThread`
+        hasMoreSelfThread: false, // populated in `annotateSelfThread`
       },
     }
   } else if (AppBskyFeedDefs.isBlockedPost(node)) {
@@ -245,6 +252,48 @@ function responseToThreadNodes(
   }
 }
 
+function annotateSelfThread(thread: ThreadNode) {
+  if (thread.type !== 'post') {
+    return
+  }
+  const selfThreadNodes: ThreadPost[] = [thread]
+
+  let parent: ThreadNode | undefined = thread.parent
+  while (parent) {
+    if (
+      parent.type !== 'post' ||
+      parent.post.author.did !== thread.post.author.did
+    ) {
+      // not a self-thread
+      return
+    }
+    selfThreadNodes.push(parent)
+    parent = parent.parent
+  }
+
+  let node = thread
+  for (let i = 0; i < 10; i++) {
+    const reply = node.replies?.find(
+      r => r.type === 'post' && r.post.author.did === thread.post.author.did,
+    )
+    if (reply?.type !== 'post') {
+      break
+    }
+    selfThreadNodes.push(reply)
+    node = reply
+  }
+
+  if (selfThreadNodes.length > 1) {
+    for (const selfThreadNode of selfThreadNodes) {
+      selfThreadNode.ctx.isSelfThread = true
+    }
+    const last = selfThreadNodes.at(-1)
+    if (last && last.post.replyCount && !last.replies?.length) {
+      last.ctx.hasMoreSelfThread = true
+    }
+  }
+}
+
 function findPostInQueryData(
   queryClient: QueryClient,
   uri: string,
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 35028334c..8061eb11c 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -34,6 +34,7 @@ import {ComposePrompt} from '../composer/Prompt'
 import {List, ListMethods} from '../util/List'
 import {ViewHeader} from '../util/ViewHeader'
 import {PostThreadItem} from './PostThreadItem'
+import {PostThreadLoadMore} from './PostThreadLoadMore'
 import {PostThreadShowHiddenReplies} from './PostThreadShowHiddenReplies'
 
 // FlatList maintainVisibleContentPosition breaks if too many items
@@ -364,6 +365,9 @@ export function PostThread({
         </View>
       )
     } else if (isThreadPost(item)) {
+      if (!treeView && item.ctx.hasMoreSelfThread) {
+        return <PostThreadLoadMore post={item.post} />
+      }
       const prev = isThreadPost(posts[index - 1])
         ? (posts[index - 1] as ThreadPost)
         : undefined
diff --git a/src/view/com/post-thread/PostThreadLoadMore.tsx b/src/view/com/post-thread/PostThreadLoadMore.tsx
new file mode 100644
index 000000000..780ea7728
--- /dev/null
+++ b/src/view/com/post-thread/PostThreadLoadMore.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react'
+import {View} from 'react-native'
+import {AppBskyFeedDefs, AtUri} from '@atproto/api'
+import {Trans} from '@lingui/macro'
+
+import {makeProfileLink} from '#/lib/routes/links'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+import {Link} from '../util/Link'
+import {UserAvatar} from '../util/UserAvatar'
+
+export function PostThreadLoadMore({post}: {post: AppBskyFeedDefs.PostView}) {
+  const t = useTheme()
+
+  const postHref = React.useMemo(() => {
+    const urip = new AtUri(post.uri)
+    return makeProfileLink(post.author, 'post', urip.rkey)
+  }, [post.uri, post.author])
+
+  return (
+    <Link
+      href={postHref}
+      style={[a.flex_row, a.align_center, a.py_md, {paddingHorizontal: 14}]}
+      hoverStyle={[t.atoms.bg_contrast_25]}>
+      <View style={[a.flex_row]}>
+        <View
+          style={{
+            alignItems: 'center',
+            justifyContent: 'center',
+            width: 34,
+            height: 34,
+            borderRadius: 18,
+            backgroundColor: t.atoms.bg.backgroundColor,
+            marginRight: -20,
+          }}>
+          <UserAvatar avatar={post.author.avatar} size={30} />
+        </View>
+        <View
+          style={{
+            alignItems: 'center',
+            justifyContent: 'center',
+            width: 34,
+            height: 34,
+            borderRadius: 18,
+            backgroundColor: t.atoms.bg.backgroundColor,
+          }}>
+          <UserAvatar avatar={post.author.avatar} size={30} />
+        </View>
+      </View>
+      <View style={[a.px_sm]}>
+        <Text style={[{color: t.palette.primary_500}, a.text_md]}>
+          <Trans>Continue thread...</Trans>
+        </Text>
+      </View>
+    </Link>
+  )
+}