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