Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 145 lines 4.0 kB view raw
1import {useCallback, useState} from 'react' 2import { 3 type AppBskyFeedDefs, 4 AppBskyFeedPost, 5 moderatePost, 6 type ModerationDecision, 7} from '@atproto/api' 8import {msg} from '@lingui/core/macro' 9import {useLingui} from '@lingui/react' 10 11import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 12import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' 13import {cleanError} from '#/lib/strings/errors' 14import {logger} from '#/logger' 15import {useModerationOpts} from '#/state/preferences/moderation-opts' 16import {usePostQuotesQuery} from '#/state/queries/post-quotes' 17import {useResolveUriQuery} from '#/state/queries/resolve-uri' 18import {Post} from '#/view/com/post/Post' 19import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 20import {List} from '../util/List' 21 22function renderItem({ 23 item, 24 index, 25}: { 26 item: { 27 post: AppBskyFeedDefs.PostView 28 moderation: ModerationDecision 29 record: AppBskyFeedPost.Record 30 } 31 index: number 32}) { 33 return <Post post={item.post} hideTopBorder={index === 0} /> 34} 35 36function keyExtractor(item: { 37 post: AppBskyFeedDefs.PostView 38 moderation: ModerationDecision 39 record: AppBskyFeedPost.Record 40}) { 41 return item.post.uri 42} 43 44export function PostQuotes({uri}: {uri: string}) { 45 const {_} = useLingui() 46 const initialNumToRender = useInitialNumToRender() 47 const [isPTRing, setIsPTRing] = useState(false) 48 const trackPostView = usePostViewTracking('PostQuotes') 49 50 const { 51 data: resolvedUri, 52 error: resolveError, 53 isLoading: isLoadingUri, 54 } = useResolveUriQuery(uri) 55 const { 56 data, 57 isLoading: isLoadingQuotes, 58 isFetchingNextPage, 59 hasNextPage, 60 fetchNextPage, 61 error, 62 refetch, 63 } = usePostQuotesQuery(resolvedUri?.uri) 64 65 const moderationOpts = useModerationOpts() 66 67 const isError = Boolean(resolveError || error) 68 69 const quotes = 70 data?.pages 71 .flatMap(page => 72 page.posts.map(post => { 73 if (!AppBskyFeedPost.isRecord(post.record) || !moderationOpts) { 74 return null 75 } 76 const moderation = moderatePost(post, moderationOpts) 77 return {post, record: post.record, moderation} 78 }), 79 ) 80 .filter(item => item !== null) ?? [] 81 82 const onRefresh = useCallback(async () => { 83 setIsPTRing(true) 84 try { 85 await refetch() 86 } catch (err) { 87 logger.error('Failed to refresh quotes', {message: err}) 88 } 89 setIsPTRing(false) 90 }, [refetch, setIsPTRing]) 91 92 const onEndReached = useCallback(async () => { 93 if (isFetchingNextPage || !hasNextPage || isError) return 94 try { 95 await fetchNextPage() 96 } catch (err) { 97 logger.error('Failed to load more quotes', {message: err}) 98 } 99 }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 100 101 if (quotes.length < 1) { 102 return ( 103 <ListMaybePlaceholder 104 isLoading={isLoadingUri || isLoadingQuotes} 105 isError={isError} 106 emptyType="results" 107 emptyTitle={_(msg`No quotes yet`)} 108 emptyMessage={_( 109 msg`Nobody has quoted this yet. Maybe you should be the first!`, 110 )} 111 errorMessage={cleanError(resolveError || error)} 112 sideBorders={false} 113 /> 114 ) 115 } 116 117 // loaded 118 // = 119 return ( 120 <List 121 data={quotes} 122 renderItem={renderItem} 123 keyExtractor={keyExtractor} 124 refreshing={isPTRing} 125 onRefresh={onRefresh} 126 onEndReached={onEndReached} 127 onEndReachedThreshold={4} 128 onItemSeen={item => trackPostView(item.post)} 129 ListFooterComponent={ 130 <ListFooter 131 isFetchingNextPage={isFetchingNextPage} 132 error={cleanError(error)} 133 onRetry={fetchNextPage} 134 showEndMessage 135 endMessageText={_(msg`That's all, folks!`)} 136 /> 137 } 138 // @ts-ignore our .web version only -prf 139 desktopFixedHeight 140 initialNumToRender={initialNumToRender} 141 windowSize={11} 142 sideBorders={false} 143 /> 144 ) 145}