mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at remove-hackfix 225 lines 6.0 kB view raw
1import {useCallback, useEffect, useMemo, useState} from 'react' 2import {LayoutAnimation, View} from 'react-native' 3import { 4 AppBskyFeedPost, 5 AppBskyRichtextFacet, 6 AtUri, 7 moderatePost, 8 RichText as RichTextAPI, 9} from '@atproto/api' 10import {msg} from '@lingui/macro' 11import {useLingui} from '@lingui/react' 12import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' 13 14import {makeProfileLink} from '#/lib/routes/links' 15import { 16 type CommonNavigatorParams, 17 type NavigationProp, 18} from '#/lib/routes/types' 19import { 20 convertBskyAppUrlIfNeeded, 21 isBskyPostUrl, 22 makeRecordUri, 23} from '#/lib/strings/url-helpers' 24import {useModerationOpts} from '#/state/preferences/moderation-opts' 25import {usePostQuery} from '#/state/queries/post' 26import {PostMeta} from '#/view/com/util/PostMeta' 27import {atoms as a, useTheme} from '#/alf' 28import {Button, ButtonIcon} from '#/components/Button' 29import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 30import {Loader} from '#/components/Loader' 31import * as MediaPreview from '#/components/MediaPreview' 32import {ContentHider} from '#/components/moderation/ContentHider' 33import {PostAlerts} from '#/components/moderation/PostAlerts' 34import {RichText} from '#/components/RichText' 35import {Text} from '#/components/Typography' 36import * as bsky from '#/types/bsky' 37 38export function useMessageEmbed() { 39 const route = 40 useRoute<RouteProp<CommonNavigatorParams, 'MessagesConversation'>>() 41 const navigation = useNavigation<NavigationProp>() 42 const embedFromParams = route.params.embed 43 44 const [embedUri, setEmbed] = useState(embedFromParams) 45 46 if (embedFromParams && embedUri !== embedFromParams) { 47 setEmbed(embedFromParams) 48 } 49 50 return { 51 embedUri, 52 setEmbed: useCallback( 53 (embedUrl: string | undefined) => { 54 if (!embedUrl) { 55 navigation.setParams({embed: ''}) 56 setEmbed(undefined) 57 return 58 } 59 60 if (embedFromParams) return 61 62 const url = convertBskyAppUrlIfNeeded(embedUrl) 63 const [_0, user, _1, rkey] = url.split('/').filter(Boolean) 64 const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey) 65 66 setEmbed(uri) 67 }, 68 [embedFromParams, navigation], 69 ), 70 } 71} 72 73export function useExtractEmbedFromFacets( 74 message: string, 75 setEmbed: (embedUrl: string | undefined) => void, 76) { 77 const rt = new RichTextAPI({text: message}) 78 rt.detectFacetsWithoutResolution() 79 80 let uriFromFacet: string | undefined 81 82 for (const facet of rt.facets ?? []) { 83 for (const feature of facet.features) { 84 if (AppBskyRichtextFacet.isLink(feature) && isBskyPostUrl(feature.uri)) { 85 uriFromFacet = feature.uri 86 break 87 } 88 } 89 } 90 91 useEffect(() => { 92 if (uriFromFacet) { 93 setEmbed(uriFromFacet) 94 } 95 }, [uriFromFacet, setEmbed]) 96} 97 98export function MessageInputEmbed({ 99 embedUri, 100 setEmbed, 101}: { 102 embedUri: string | undefined 103 setEmbed: (embedUrl: string | undefined) => void 104}) { 105 const t = useTheme() 106 const {_} = useLingui() 107 108 const {data: post, status} = usePostQuery(embedUri) 109 110 const moderationOpts = useModerationOpts() 111 const moderation = useMemo( 112 () => 113 moderationOpts && post ? moderatePost(post, moderationOpts) : undefined, 114 [moderationOpts, post], 115 ) 116 117 const {rt, record} = useMemo(() => { 118 if ( 119 post && 120 bsky.dangerousIsType<AppBskyFeedPost.Record>( 121 post.record, 122 AppBskyFeedPost.isRecord, 123 ) 124 ) { 125 return { 126 rt: new RichTextAPI({ 127 text: post.record.text, 128 facets: post.record.facets, 129 }), 130 record: post.record, 131 } 132 } 133 134 return {rt: undefined, record: undefined} 135 }, [post]) 136 137 if (!embedUri) { 138 return null 139 } 140 141 let content = null 142 switch (status) { 143 case 'pending': 144 content = ( 145 <View 146 style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}> 147 <Loader /> 148 </View> 149 ) 150 break 151 case 'error': 152 content = ( 153 <View 154 style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}> 155 <Text style={a.text_center}>Could not fetch post</Text> 156 </View> 157 ) 158 break 159 case 'success': 160 const itemUrip = new AtUri(post.uri) 161 const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey) 162 163 if (!post || !moderation || !rt || !record) { 164 return null 165 } 166 167 content = ( 168 <View 169 style={[ 170 a.flex_1, 171 t.atoms.bg, 172 t.atoms.border_contrast_low, 173 a.rounded_md, 174 a.border, 175 a.p_sm, 176 a.mb_sm, 177 ]} 178 pointerEvents="none"> 179 <PostMeta 180 showAvatar 181 author={post.author} 182 moderation={moderation} 183 timestamp={post.indexedAt} 184 postHref={itemHref} 185 style={a.flex_0} 186 /> 187 <ContentHider modui={moderation.ui('contentView')}> 188 <PostAlerts modui={moderation.ui('contentView')} style={a.py_xs} /> 189 {rt.text && ( 190 <View style={a.mt_xs}> 191 <RichText 192 enableTags 193 testID="postText" 194 value={rt} 195 style={[a.text_sm, t.atoms.text_contrast_high]} 196 authorHandle={post.author.handle} 197 numberOfLines={3} 198 /> 199 </View> 200 )} 201 <MediaPreview.Embed embed={post.embed} style={a.mt_sm} /> 202 </ContentHider> 203 </View> 204 ) 205 break 206 } 207 208 return ( 209 <View style={[a.flex_row, a.gap_sm]}> 210 {content} 211 <Button 212 label={_(msg`Remove embed`)} 213 onPress={() => { 214 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 215 setEmbed(undefined) 216 }} 217 size="tiny" 218 variant="solid" 219 color="secondary" 220 shape="round"> 221 <ButtonIcon icon={X} /> 222 </Button> 223 </View> 224 ) 225}