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