Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

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

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