mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {
2 type AppBskyFeedDefs,
3 AppBskyFeedPost,
4 AppBskyFeedThreadgate,
5 AppBskyUnspeccedDefs,
6 type AppBskyUnspeccedGetPostThreadV2,
7 AtUri,
8} from '@atproto/api'
9
10import {
11 type ApiThreadItem,
12 type ThreadItem,
13 type TraversalMetadata,
14} from '#/state/queries/usePostThread/types'
15import {isDevMode} from '#/storage/hooks/dev-mode'
16import * as bsky from '#/types/bsky'
17
18export function getThreadgateRecord(
19 view: AppBskyUnspeccedGetPostThreadV2.OutputSchema['threadgate'],
20) {
21 return bsky.dangerousIsType<AppBskyFeedThreadgate.Record>(
22 view?.record,
23 AppBskyFeedThreadgate.isRecord,
24 )
25 ? view?.record
26 : undefined
27}
28
29export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) {
30 if (
31 bsky.dangerousIsType<AppBskyFeedPost.Record>(
32 post.record,
33 AppBskyFeedPost.isRecord,
34 )
35 ) {
36 if (post.record.reply?.root?.uri) {
37 return new AtUri(post.record.reply.root.uri)
38 }
39 }
40}
41
42export function getPostRecord(post: AppBskyFeedDefs.PostView) {
43 return post.record as AppBskyFeedPost.Record
44}
45
46export function getTraversalMetadata({
47 item,
48 prevItem,
49 nextItem,
50 parentMetadata,
51}: {
52 item: ApiThreadItem
53 prevItem?: ApiThreadItem
54 nextItem?: ApiThreadItem
55 parentMetadata?: TraversalMetadata
56}): TraversalMetadata {
57 if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
58 throw new Error(`Expected thread item to be a post`)
59 }
60 const repliesCount = item.value.post.replyCount || 0
61 const repliesUnhydrated = item.value.moreReplies || 0
62 const metadata = {
63 depth: item.depth,
64 /*
65 * Unknown until after traversal
66 */
67 isLastChild: false,
68 /*
69 * Unknown until after traversal
70 */
71 isLastSibling: false,
72 /*
73 * If it's a top level reply, bc we render each top-level branch as a
74 * separate tree, it's implicitly part of the last branch. For subsequent
75 * replies, we'll override this after traversal.
76 */
77 isPartOfLastBranchFromDepth: item.depth === 1 ? 1 : undefined,
78 nextItemDepth: nextItem?.depth,
79 parentMetadata,
80 prevItemDepth: prevItem?.depth,
81 /*
82 * Unknown until after traversal
83 */
84 precedesChildReadMore: false,
85 /*
86 * Unknown until after traversal
87 */
88 followsReadMoreUp: false,
89 postData: {
90 uri: item.uri,
91 authorHandle: item.value.post.author.handle,
92 },
93 repliesCount,
94 repliesUnhydrated,
95 repliesSeenCounter: 0,
96 repliesIndexCounter: 0,
97 replyIndex: 0,
98 skippedIndentIndices: new Set<number>(),
99 }
100
101 if (isDevMode()) {
102 // @ts-ignore dev only for debugging
103 metadata.postData.text = getPostRecord(item.value.post).text
104 }
105
106 return metadata
107}
108
109export function storeTraversalMetadata(
110 metadatas: Map<string, TraversalMetadata>,
111 metadata: TraversalMetadata,
112) {
113 metadatas.set(metadata.postData.uri, metadata)
114
115 if (isDevMode()) {
116 // @ts-ignore dev only for debugging
117 metadatas.set(metadata.postData.text, metadata)
118 // @ts-ignore
119 window.__thread = metadatas
120 }
121}
122
123export function getThreadPostUI({
124 depth,
125 repliesCount,
126 prevItemDepth,
127 isLastChild,
128 skippedIndentIndices,
129 repliesSeenCounter,
130 repliesUnhydrated,
131 precedesChildReadMore,
132 followsReadMoreUp,
133}: TraversalMetadata): Extract<ThreadItem, {type: 'threadPost'}>['ui'] {
134 const isReplyAndHasReplies =
135 depth > 0 &&
136 repliesCount > 0 &&
137 (repliesCount - repliesUnhydrated === repliesSeenCounter ||
138 repliesSeenCounter > 0)
139 return {
140 isAnchor: depth === 0,
141 showParentReplyLine:
142 followsReadMoreUp ||
143 (!!prevItemDepth && prevItemDepth !== 0 && prevItemDepth < depth),
144 showChildReplyLine: depth < 0 || isReplyAndHasReplies,
145 indent: depth,
146 /*
147 * If there are no slices below this one, or the next slice has a depth <=
148 * than the depth of this post, it's the last child of the reply tree. It
149 * is not necessarily the last leaf in the parent branch, since it could
150 * have another sibling.
151 */
152 isLastChild,
153 skippedIndentIndices,
154 precedesChildReadMore: precedesChildReadMore ?? false,
155 }
156}
157
158export function getThreadPostNoUnauthenticatedUI({
159 depth,
160 prevItemDepth,
161}: {
162 depth: number
163 prevItemDepth?: number
164 nextItemDepth?: number
165}): Extract<ThreadItem, {type: 'threadPostNoUnauthenticated'}>['ui'] {
166 return {
167 showChildReplyLine: depth < 0,
168 showParentReplyLine: Boolean(prevItemDepth && prevItemDepth < depth),
169 }
170}