Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 168 lines 4.3 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import { 4 type AppBskyGraphDefs, 5 AtUri, 6 moderateUserList, 7 type ModerationUI, 8} from '@atproto/api' 9import {msg} from '@lingui/core/macro' 10import {useLingui} from '@lingui/react' 11import {Trans} from '@lingui/react/macro' 12import {useQueryClient} from '@tanstack/react-query' 13 14import {sanitizeHandle} from '#/lib/strings/handles' 15import {useModerationOpts} from '#/state/preferences/moderation-opts' 16import {precacheList} from '#/state/queries/feed' 17import {useSession} from '#/state/session' 18import {atoms as a, useTheme} from '#/alf' 19import { 20 Avatar, 21 Description, 22 Header, 23 Outer, 24 SaveButton, 25} from '#/components/FeedCard' 26import {Link as InternalLink, type LinkProps} from '#/components/Link' 27import * as Hider from '#/components/moderation/Hider' 28import {Text} from '#/components/Typography' 29import type * as bsky from '#/types/bsky' 30 31/* 32 * This component is based on `FeedCard` and is tightly coupled with that 33 * component. Please refer to `FeedCard` for more context. 34 */ 35 36export { 37 Avatar, 38 AvatarPlaceholder, 39 Description, 40 Header, 41 Outer, 42 SaveButton, 43 TitleAndBylinePlaceholder, 44} from '#/components/FeedCard' 45 46const CURATELIST = 'app.bsky.graph.defs#curatelist' 47const MODLIST = 'app.bsky.graph.defs#modlist' 48 49type Props = { 50 view: AppBskyGraphDefs.ListView 51 showPinButton?: boolean 52} 53 54export function Default( 55 props: Props & Omit<LinkProps, 'to' | 'label' | 'children'>, 56) { 57 const {view, showPinButton} = props 58 const moderationOpts = useModerationOpts() 59 const moderation = moderationOpts 60 ? moderateUserList(view, moderationOpts) 61 : undefined 62 63 return ( 64 <Link {...props}> 65 <Outer> 66 <Header> 67 <Avatar src={view.avatar} /> 68 <TitleAndByline 69 title={view.name} 70 creator={view.creator} 71 purpose={view.purpose} 72 modUi={moderation?.ui('contentView')} 73 /> 74 {showPinButton && view.purpose === CURATELIST && ( 75 <SaveButton view={view} pin /> 76 )} 77 </Header> 78 <Description description={view.description} /> 79 </Outer> 80 </Link> 81 ) 82} 83 84export function Link({ 85 view, 86 children, 87 ...props 88}: Props & Omit<LinkProps, 'to' | 'label'>) { 89 const queryClient = useQueryClient() 90 91 const href = React.useMemo(() => { 92 return createProfileListHref({list: view}) 93 }, [view]) 94 95 React.useEffect(() => { 96 precacheList(queryClient, view) 97 }, [view, queryClient]) 98 99 return ( 100 <InternalLink label={view.name} to={href} {...props}> 101 {children} 102 </InternalLink> 103 ) 104} 105 106export function TitleAndByline({ 107 title, 108 creator, 109 purpose = CURATELIST, 110 modUi, 111}: { 112 title: string 113 creator?: bsky.profile.AnyProfileView 114 purpose?: AppBskyGraphDefs.ListView['purpose'] 115 modUi?: ModerationUI 116}) { 117 const t = useTheme() 118 const {_} = useLingui() 119 const {currentAccount} = useSession() 120 121 return ( 122 <View style={[a.flex_1]}> 123 <Hider.Outer 124 modui={modUi} 125 isContentVisibleInitialState={ 126 creator && currentAccount?.did === creator.did 127 } 128 allowOverride={creator && currentAccount?.did === creator.did}> 129 <Hider.Mask> 130 <Text 131 style={[a.text_md, a.font_semi_bold, a.leading_snug, a.italic]} 132 numberOfLines={1}> 133 <Trans>Hidden list</Trans> 134 </Text> 135 </Hider.Mask> 136 <Hider.Content> 137 <Text 138 emoji 139 style={[a.text_md, a.font_semi_bold, a.leading_snug]} 140 numberOfLines={1}> 141 {title} 142 </Text> 143 </Hider.Content> 144 </Hider.Outer> 145 146 {creator && ( 147 <Text 148 emoji 149 style={[a.leading_snug, t.atoms.text_contrast_medium]} 150 numberOfLines={1}> 151 {purpose === MODLIST 152 ? _(msg`Moderation list by ${sanitizeHandle(creator.handle, '@')}`) 153 : _(msg`List by ${sanitizeHandle(creator.handle, '@')}`)} 154 </Text> 155 )} 156 </View> 157 ) 158} 159 160export function createProfileListHref({ 161 list, 162}: { 163 list: AppBskyGraphDefs.ListView 164}) { 165 const urip = new AtUri(list.uri) 166 const handleOrDid = list.creator.handle || list.creator.did 167 return `/profile/${handleOrDid}/lists/${urip.rkey}` 168}