forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {memo, useMemo} from 'react'
2import {AtUri} from '@atproto/api'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5import {useNavigation} from '@react-navigation/native'
6
7import {useOpenLink} from '#/lib/hooks/useOpenLink'
8import {makeProfileLink} from '#/lib/routes/links'
9import {type NavigationProp} from '#/lib/routes/types'
10import {shareText, shareUrl} from '#/lib/sharing'
11import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology'
12import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers'
13import {logger} from '#/logger'
14import {isWeb} from '#/platform/detection'
15import {useProfileShadow} from '#/state/cache/profile-shadow'
16import {useTerminologyPreference} from '#/state/preferences'
17import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons'
18import {useSession} from '#/state/session'
19import {useBreakpoints} from '#/alf'
20import {useDialogControl} from '#/components/Dialog'
21import {EmbedDialog} from '#/components/dialogs/Embed'
22import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
23import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
24import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
25import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
26import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
27import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight'
28import * as Menu from '#/components/Menu'
29import {useAgeAssurance} from '#/ageAssurance'
30import {useDevMode} from '#/storage/hooks/dev-mode'
31import {type ShareMenuItemsProps} from './ShareMenuItems.types'
32
33let ShareMenuItems = ({
34 post,
35 record,
36 timestamp,
37 onShare: onShareProp,
38}: ShareMenuItemsProps): React.ReactNode => {
39 const {hasSession} = useSession()
40 const {gtMobile} = useBreakpoints()
41 const {_} = useLingui()
42 const terminologyPreference = useTerminologyPreference()
43 const navigation = useNavigation<NavigationProp>()
44 const embedPostControl = useDialogControl()
45 const sendViaChatControl = useDialogControl()
46 const [devModeEnabled] = useDevMode()
47 const aa = useAgeAssurance()
48 const openLink = useOpenLink()
49
50 const postUri = post.uri
51 const postCid = post.cid
52 const postAuthor = useProfileShadow(post.author)
53
54 const href = useMemo(() => {
55 const urip = new AtUri(postUri)
56 return makeProfileLink(postAuthor, 'post', urip.rkey)
57 }, [postUri, postAuthor])
58
59 const hideInPWI = useMemo(() => {
60 return !!postAuthor.labels?.find(
61 label => label.val === '!no-unauthenticated',
62 )
63 }, [postAuthor])
64
65 const onCopyLink = () => {
66 logger.metric('share:press:copyLink', {}, {statsig: true})
67 const url = toShareUrl(href)
68 shareUrl(url)
69 onShareProp()
70 }
71
72 const onCopyLinkBsky = () => {
73 logger.metric('share:press:copyLink', {}, {statsig: true})
74 const url = toShareUrlBsky(href)
75 shareUrl(url)
76 onShareProp()
77 }
78
79 const onSelectChatToShareTo = (conversation: string) => {
80 logger.metric('share:press:dmSelected', {}, {statsig: true})
81 navigation.navigate('MessagesConversation', {
82 conversation,
83 embed: postUri,
84 })
85 }
86
87 const canEmbed = isWeb && gtMobile && !hideInPWI
88
89 const onShareATURI = () => {
90 shareText(postUri)
91 }
92
93 const onShareAuthorDID = () => {
94 shareText(postAuthor.did)
95 }
96
97 const showExternalShareButtons = useShowExternalShareButtons()
98 const isBridgedPost =
99 !!post.record.bridgyOriginalUrl || !!post.record.fediverseId
100 const originalPostUrl = (post.record.bridgyOriginalUrl ||
101 post.record.fediverseId) as string | undefined
102
103 const onOpenOriginalPost = () => {
104 originalPostUrl && openLink(originalPostUrl, true)
105 }
106
107 const onOpenPostInPdsls = () => {
108 openLink(`https://pdsls.dev/${post.uri}`, true)
109 }
110
111 const copyLinkItem = (
112 <Menu.Group>
113 <Menu.Item
114 testID="postDropdownShareBtn"
115 label={_(getTerminology(terminologyPreference, {
116 skeet: msg`Copy link to skeet`,
117 post: msg`Copy link to post`,
118 spell: msg`Copy link to spell`,
119 }))}
120 onPress={onCopyLink}>
121 <Menu.ItemText>
122 <Trans>{_(getTerminology(terminologyPreference, {
123 skeet: msg`Copy link to skeet`,
124 post: msg`Copy link to post`,
125 spell: msg`Copy link to spell`,
126 }))}</Trans>
127 </Menu.ItemText>
128 <Menu.ItemIcon icon={ChainLinkIcon} position="right" />
129 </Menu.Item>
130 <Menu.Item
131 testID="postDropdownShareBtn"
132 label={_(msg`Copy link to post`)}
133 onPress={onCopyLinkBsky}>
134 <Menu.ItemText>
135 <Trans>Copy via bsky.app</Trans>
136 </Menu.ItemText>
137 <Menu.ItemIcon icon={ChainLinkIcon} position="right" />
138 </Menu.Item>
139 </Menu.Group>
140 )
141
142 return (
143 <>
144 <Menu.Outer>
145 {copyLinkItem}
146
147 {showExternalShareButtons && isBridgedPost && (
148 <Menu.Item
149 testID="postDropdownOpenOriginalPost"
150 label={_(getTerminology(terminologyPreference, {
151 skeet: msg`Open original skeet`,
152 post: msg`Open original post`,
153 spell: msg`Open original spell`,
154 }))}
155 onPress={onOpenOriginalPost}>
156 <Menu.ItemText>
157 <Trans>{_(getTerminology(terminologyPreference, {
158 skeet: msg`Open original skeet`,
159 post: msg`Open original post`,
160 spell: msg`Open original spell`,
161 }))}</Trans>
162 </Menu.ItemText>
163 <Menu.ItemIcon icon={ExternalIcon} position="right" />
164 </Menu.Item>
165 )}
166
167 {showExternalShareButtons && (
168 <Menu.Item
169 testID="postDropdownOpenInPdsls"
170 label={_(getTerminology(terminologyPreference, {
171 skeet: msg`Open skeet in PDSls`,
172 post: msg`Open post in PDSls`,
173 spell: msg`Open spell in PDSls`,
174 }))}
175 onPress={onOpenPostInPdsls}>
176 <Menu.ItemText>
177 <Trans>{_(getTerminology(terminologyPreference, {
178 skeet: msg`Open skeet in PDSls`,
179 post: msg`Open post in PDSls`,
180 spell: msg`Open spell in PDSls`,
181 }))}</Trans>
182 </Menu.ItemText>
183 <Menu.ItemIcon icon={ExternalIcon} position="right" />
184 </Menu.Item>
185 )}
186
187 {hasSession && aa.state.access === aa.Access.Full && (
188 <Menu.Item
189 testID="postDropdownSendViaDMBtn"
190 label={_(msg`Send via direct message`)}
191 onPress={() => {
192 logger.metric('share:press:openDmSearch', {}, {statsig: true})
193 sendViaChatControl.open()
194 }}>
195 <Menu.ItemText>
196 <Trans>Send via direct message</Trans>
197 </Menu.ItemText>
198 <Menu.ItemIcon icon={Send} position="right" />
199 </Menu.Item>
200 )}
201
202 {canEmbed && (
203 <Menu.Item
204 testID="postDropdownEmbedBtn"
205 label={_(getTerminology(terminologyPreference, {
206 skeet: msg`Embed skeet`,
207 post: msg`Embed post`,
208 spell: msg`Embed spell`,
209 }))}
210 onPress={() => {
211 logger.metric('share:press:embed', {}, {statsig: true})
212 embedPostControl.open()
213 }}>
214 <Menu.ItemText>{_(getTerminology(terminologyPreference, {
215 skeet: msg`Embed skeet`,
216 post: msg`Embed post`,
217 spell: msg`Embed spell`,
218 }))}</Menu.ItemText>
219 <Menu.ItemIcon icon={CodeBracketsIcon} position="right" />
220 </Menu.Item>
221 )}
222
223 {false && hideInPWI && (
224 <>
225 {hasSession && <Menu.Divider />}
226 {copyLinkItem}
227 <Menu.LabelText style={{maxWidth: 220}}>
228 <Trans>{_(getTerminology(terminologyPreference, {
229 skeet: msg`Note: This skeet is only visible to logged-in users.`,
230 post: msg`Note: This post is only visible to logged-in users.`,
231 spell: msg`Note: This spell is only visible to logged-in users.`,
232 }))}</Trans>
233 </Menu.LabelText>
234 </>
235 )}
236
237 {devModeEnabled && (
238 <>
239 <Menu.Divider />
240 <Menu.Item
241 testID="postAtUriShareBtn"
242 label={_(getTerminology(terminologyPreference, {
243 skeet: msg`Copy skeet at:// URI`,
244 post: msg`Copy post at:// URI`,
245 spell: msg`Copy spell at:// URI`,
246 }))}
247 onPress={onShareATURI}>
248 <Menu.ItemText>
249 <Trans>{_(getTerminology(terminologyPreference, {
250 skeet: msg`Copy skeet at:// URI`,
251 post: msg`Copy post at:// URI`,
252 spell: msg`Copy spell at:// URI`,
253 }))}</Trans>
254 </Menu.ItemText>
255 <Menu.ItemIcon icon={ClipboardIcon} position="right" />
256 </Menu.Item>
257 <Menu.Item
258 testID="postAuthorDIDShareBtn"
259 label={_(msg`Copy author DID`)}
260 onPress={onShareAuthorDID}>
261 <Menu.ItemText>
262 <Trans>Copy author DID</Trans>
263 </Menu.ItemText>
264 <Menu.ItemIcon icon={ClipboardIcon} position="right" />
265 </Menu.Item>
266 </>
267 )}
268 </Menu.Outer>
269
270 {canEmbed && (
271 <EmbedDialog
272 control={embedPostControl}
273 postCid={postCid}
274 postUri={postUri}
275 record={record}
276 postAuthor={postAuthor}
277 timestamp={timestamp}
278 />
279 )}
280
281 <SendViaChatDialog
282 control={sendViaChatControl}
283 onSelectChat={onSelectChatToShareTo}
284 />
285 </>
286 )
287}
288ShareMenuItems = memo(ShareMenuItems)
289export {ShareMenuItems}