Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
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}