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