mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at offline-detection 259 lines 7.4 kB view raw
1import React, {useState, useMemo} from 'react' 2import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3import { 4 AppBskyFeedDefs, 5 AppBskyFeedPost, 6 AtUri, 7 PostModeration, 8 RichText as RichTextAPI, 9} from '@atproto/api' 10import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' 11import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12import {Link, TextLink} from '../util/Link' 13import {UserInfoText} from '../util/UserInfoText' 14import {PostMeta} from '../util/PostMeta' 15import {PostEmbeds} from '../util/post-embeds' 16import {PostCtrls} from '../util/post-ctrls/PostCtrls' 17import {ContentHider} from '../util/moderation/ContentHider' 18import {PostAlerts} from '../util/moderation/PostAlerts' 19import {Text} from '../util/text/Text' 20import {RichText} from '../util/text/RichText' 21import {PreviewableUserAvatar} from '../util/UserAvatar' 22import {s, colors} from 'lib/styles' 23import {usePalette} from 'lib/hooks/usePalette' 24import {makeProfileLink} from 'lib/routes/links' 25import {MAX_POST_LINES} from 'lib/constants' 26import {countLines} from 'lib/strings/helpers' 27import {useModerationOpts} from '#/state/queries/preferences' 28import {useComposerControls} from '#/state/shell/composer' 29import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' 30 31export function Post({ 32 post, 33 showReplyLine, 34 style, 35}: { 36 post: AppBskyFeedDefs.PostView 37 showReplyLine?: boolean 38 style?: StyleProp<ViewStyle> 39}) { 40 const moderationOpts = useModerationOpts() 41 const record = useMemo<AppBskyFeedPost.Record | undefined>( 42 () => 43 AppBskyFeedPost.isRecord(post.record) && 44 AppBskyFeedPost.validateRecord(post.record).success 45 ? post.record 46 : undefined, 47 [post], 48 ) 49 const postShadowed = usePostShadow(post) 50 const richText = useMemo( 51 () => 52 record 53 ? new RichTextAPI({ 54 text: record.text, 55 facets: record.facets, 56 }) 57 : undefined, 58 [record], 59 ) 60 const moderation = useMemo( 61 () => (moderationOpts ? moderatePost(post, moderationOpts) : undefined), 62 [moderationOpts, post], 63 ) 64 if (postShadowed === POST_TOMBSTONE) { 65 return null 66 } 67 if (record && richText && moderation) { 68 return ( 69 <PostInner 70 post={postShadowed} 71 record={record} 72 richText={richText} 73 moderation={moderation} 74 showReplyLine={showReplyLine} 75 style={style} 76 /> 77 ) 78 } 79 return null 80} 81 82function PostInner({ 83 post, 84 record, 85 richText, 86 moderation, 87 showReplyLine, 88 style, 89}: { 90 post: Shadow<AppBskyFeedDefs.PostView> 91 record: AppBskyFeedPost.Record 92 richText: RichTextAPI 93 moderation: PostModeration 94 showReplyLine?: boolean 95 style?: StyleProp<ViewStyle> 96}) { 97 const pal = usePalette('default') 98 const {openComposer} = useComposerControls() 99 const [limitLines, setLimitLines] = useState( 100 () => countLines(richText?.text) >= MAX_POST_LINES, 101 ) 102 const itemUrip = new AtUri(post.uri) 103 const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey) 104 let replyAuthorDid = '' 105 if (record.reply) { 106 const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri) 107 replyAuthorDid = urip.hostname 108 } 109 110 const onPressReply = React.useCallback(() => { 111 openComposer({ 112 replyTo: { 113 uri: post.uri, 114 cid: post.cid, 115 text: record.text, 116 author: { 117 handle: post.author.handle, 118 displayName: post.author.displayName, 119 avatar: post.author.avatar, 120 }, 121 }, 122 }) 123 }, [openComposer, post, record]) 124 125 const onPressShowMore = React.useCallback(() => { 126 setLimitLines(false) 127 }, [setLimitLines]) 128 129 return ( 130 <Link href={itemHref} style={[styles.outer, pal.view, pal.border, style]}> 131 {showReplyLine && <View style={styles.replyLine} />} 132 <View style={styles.layout}> 133 <View style={styles.layoutAvi}> 134 <PreviewableUserAvatar 135 size={52} 136 did={post.author.did} 137 handle={post.author.handle} 138 avatar={post.author.avatar} 139 moderation={moderation.avatar} 140 /> 141 </View> 142 <View style={styles.layoutContent}> 143 <PostMeta 144 author={post.author} 145 authorHasWarning={!!post.author.labels?.length} 146 timestamp={post.indexedAt} 147 postHref={itemHref} 148 /> 149 {replyAuthorDid !== '' && ( 150 <View style={[s.flexRow, s.mb2, s.alignCenter]}> 151 <FontAwesomeIcon 152 icon="reply" 153 size={9} 154 style={[pal.textLight, s.mr5]} 155 /> 156 <Text 157 type="sm" 158 style={[pal.textLight, s.mr2]} 159 lineHeight={1.2} 160 numberOfLines={1}> 161 Reply to{' '} 162 <UserInfoText 163 type="sm" 164 did={replyAuthorDid} 165 attr="displayName" 166 style={[pal.textLight]} 167 /> 168 </Text> 169 </View> 170 )} 171 <ContentHider 172 moderation={moderation.content} 173 style={styles.contentHider} 174 childContainerStyle={styles.contentHiderChild}> 175 <PostAlerts moderation={moderation.content} style={styles.alert} /> 176 {richText.text ? ( 177 <View style={styles.postTextContainer}> 178 <RichText 179 testID="postText" 180 type="post-text" 181 richText={richText} 182 lineHeight={1.3} 183 numberOfLines={limitLines ? MAX_POST_LINES : undefined} 184 style={s.flex1} 185 /> 186 </View> 187 ) : undefined} 188 {limitLines ? ( 189 <TextLink 190 text="Show More" 191 style={pal.link} 192 onPress={onPressShowMore} 193 href="#" 194 /> 195 ) : undefined} 196 {post.embed ? ( 197 <ContentHider 198 moderation={moderation.embed} 199 moderationDecisions={moderation.decisions} 200 ignoreQuoteDecisions 201 style={styles.contentHider}> 202 <PostEmbeds 203 embed={post.embed} 204 moderation={moderation.embed} 205 moderationDecisions={moderation.decisions} 206 /> 207 </ContentHider> 208 ) : null} 209 </ContentHider> 210 <PostCtrls post={post} record={record} onPressReply={onPressReply} /> 211 </View> 212 </View> 213 </Link> 214 ) 215} 216 217const styles = StyleSheet.create({ 218 outer: { 219 paddingTop: 10, 220 paddingRight: 15, 221 paddingBottom: 5, 222 paddingLeft: 10, 223 borderTopWidth: 1, 224 // @ts-ignore web only -prf 225 cursor: 'pointer', 226 }, 227 layout: { 228 flexDirection: 'row', 229 }, 230 layoutAvi: { 231 width: 70, 232 paddingLeft: 8, 233 }, 234 layoutContent: { 235 flex: 1, 236 }, 237 alert: { 238 marginBottom: 6, 239 }, 240 postTextContainer: { 241 flexDirection: 'row', 242 alignItems: 'center', 243 flexWrap: 'wrap', 244 }, 245 replyLine: { 246 position: 'absolute', 247 left: 36, 248 top: 70, 249 bottom: 0, 250 borderLeftWidth: 2, 251 borderLeftColor: colors.gray2, 252 }, 253 contentHider: { 254 marginBottom: 2, 255 }, 256 contentHiderChild: { 257 marginTop: 6, 258 }, 259})