Bluesky app fork with some witchin' additions 馃挮
at post-text-option 289 lines 10 kB view raw
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}