Bluesky app fork with some witchin' additions 馃挮
at main 175 lines 4.6 kB view raw
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 replyIndex: 0, 103 skippedIndentIndices: new Set<number>(), 104 } 105 106 if (isDevMode()) { 107 // @ts-ignore dev only for debugging 108 metadata.postData.text = getPostRecord(item.value.post).text 109 } 110 111 return metadata 112} 113 114export function storeTraversalMetadata( 115 metadatas: Map<string, TraversalMetadata>, 116 metadata: TraversalMetadata, 117) { 118 metadatas.set(metadata.postData.uri, metadata) 119 120 if (isDevMode()) { 121 // @ts-ignore dev only for debugging 122 metadatas.set(metadata.postData.text, metadata) 123 // @ts-ignore 124 window.__thread = metadatas 125 } 126} 127 128export function getThreadPostUI({ 129 depth, 130 repliesCount, 131 prevItemDepth, 132 isLastChild, 133 skippedIndentIndices, 134 repliesSeenCounter, 135 repliesUnhydrated, 136 precedesChildReadMore, 137 followsReadMoreUp, 138}: TraversalMetadata): Extract<ThreadItem, {type: 'threadPost'}>['ui'] { 139 const isReplyAndHasReplies = 140 depth > 0 && 141 repliesCount > 0 && 142 (repliesCount - repliesUnhydrated === repliesSeenCounter || 143 repliesSeenCounter > 0) 144 return { 145 isAnchor: depth === 0, 146 showParentReplyLine: 147 followsReadMoreUp || 148 (!!prevItemDepth && prevItemDepth !== 0 && prevItemDepth < depth), 149 showChildReplyLine: depth < 0 || isReplyAndHasReplies, 150 indent: depth, 151 /* 152 * If there are no slices below this one, or the next slice has a depth <= 153 * than the depth of this post, it's the last child of the reply tree. It 154 * is not necessarily the last leaf in the parent branch, since it could 155 * have another sibling. 156 */ 157 isLastChild, 158 skippedIndentIndices, 159 precedesChildReadMore: precedesChildReadMore ?? false, 160 } 161} 162 163export function getThreadPostNoUnauthenticatedUI({ 164 depth, 165 prevItemDepth, 166}: { 167 depth: number 168 prevItemDepth?: number 169 nextItemDepth?: number 170}): Extract<ThreadItem, {type: 'threadPostNoUnauthenticated'}>['ui'] { 171 return { 172 showChildReplyLine: depth < 0, 173 showParentReplyLine: Boolean(prevItemDepth && prevItemDepth < depth), 174 } 175}