Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replace "Note about sharing" prompt with an inline hint (#8452)

* add pwi warning to share menu, remove prompt

* add pwi label to web, remove prompt

* add an option to the PWI menu

* conditionally reorder items on web

authored by

Samuel Newman and committed by
GitHub
487da69a 23a7bc50

+142 -146
+28 -17
src/components/Menu/index.tsx
··· 1 - import React from 'react' 2 - import {Pressable, StyleProp, View, ViewStyle} from 'react-native' 1 + import {cloneElement, Fragment, isValidElement, useMemo} from 'react' 2 + import { 3 + Pressable, 4 + type StyleProp, 5 + type TextStyle, 6 + View, 7 + type ViewStyle, 8 + } from 'react-native' 3 9 import {msg, Trans} from '@lingui/macro' 4 10 import {useLingui} from '@lingui/react' 5 11 import flattenReactChildren from 'react-keyed-flatten-children' ··· 16 22 useMenuItemContext, 17 23 } from '#/components/Menu/context' 18 24 import { 19 - ContextType, 20 - GroupProps, 21 - ItemIconProps, 22 - ItemProps, 23 - ItemTextProps, 24 - TriggerProps, 25 + type ContextType, 26 + type GroupProps, 27 + type ItemIconProps, 28 + type ItemProps, 29 + type ItemTextProps, 30 + type TriggerProps, 25 31 } from '#/components/Menu/types' 26 32 import {Text} from '#/components/Typography' 27 33 ··· 39 45 control?: Dialog.DialogControlProps 40 46 }>) { 41 47 const defaultControl = Dialog.useDialogControl() 42 - const context = React.useMemo<ContextType>( 48 + const context = useMemo<ContextType>( 43 49 () => ({ 44 50 control: control || defaultControl, 45 51 }), ··· 276 282 ) 277 283 } 278 284 279 - export function LabelText({children}: {children: React.ReactNode}) { 285 + export function LabelText({ 286 + children, 287 + style, 288 + }: { 289 + children: React.ReactNode 290 + style?: StyleProp<TextStyle> 291 + }) { 280 292 const t = useTheme() 281 293 return ( 282 294 <Text 283 295 style={[ 284 296 a.font_bold, 285 297 t.atoms.text_contrast_medium, 286 - { 287 - marginBottom: -8, 288 - }, 298 + {marginBottom: -8}, 299 + style, 289 300 ]}> 290 301 {children} 291 302 </Text> ··· 304 315 style, 305 316 ]}> 306 317 {flattenReactChildren(children).map((child, i) => { 307 - return React.isValidElement(child) && 318 + return isValidElement(child) && 308 319 (child.type === Item || child.type === ContainerItem) ? ( 309 - <React.Fragment key={i}> 320 + <Fragment key={i}> 310 321 {i > 0 ? ( 311 322 <View style={[a.border_b, t.atoms.border_contrast_low]} /> 312 323 ) : null} 313 - {React.cloneElement(child, { 324 + {cloneElement(child, { 314 325 // @ts-expect-error cloneElement is not aware of the types 315 326 style: { 316 327 borderRadius: 0, 317 328 borderWidth: 0, 318 329 }, 319 330 })} 320 - </React.Fragment> 331 + </Fragment> 321 332 ) : null 322 333 })} 323 334 </View>
+25 -14
src/components/Menu/index.web.tsx
··· 1 - import React from 'react' 2 - import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' 1 + import {forwardRef, useCallback, useId, useMemo, useState} from 'react' 2 + import { 3 + Pressable, 4 + type StyleProp, 5 + type TextStyle, 6 + View, 7 + type ViewStyle, 8 + } from 'react-native' 3 9 import {msg} from '@lingui/macro' 4 10 import {useLingui} from '@lingui/react' 5 11 import {DropdownMenu} from 'radix-ui' ··· 29 35 export {useMenuContext} 30 36 31 37 export function useMenuControl(): Dialog.DialogControlProps { 32 - const id = React.useId() 33 - const [isOpen, setIsOpen] = React.useState(false) 38 + const id = useId() 39 + const [isOpen, setIsOpen] = useState(false) 34 40 35 - return React.useMemo( 41 + return useMemo( 36 42 () => ({ 37 43 id, 38 44 ref: {current: null}, ··· 56 62 }>) { 57 63 const {_} = useLingui() 58 64 const defaultControl = useMenuControl() 59 - const context = React.useMemo<ContextType>( 65 + const context = useMemo<ContextType>( 60 66 () => ({ 61 67 control: control || defaultControl, 62 68 }), 63 69 [control, defaultControl], 64 70 ) 65 - const onOpenChange = React.useCallback( 71 + const onOpenChange = useCallback( 66 72 (open: boolean) => { 67 73 if (context.control.isOpen && !open) { 68 74 context.control.close() ··· 96 102 ) 97 103 } 98 104 99 - const RadixTriggerPassThrough = React.forwardRef( 105 + const RadixTriggerPassThrough = forwardRef( 100 106 ( 101 107 props: { 102 108 children: ( ··· 355 361 ) 356 362 } 357 363 358 - export function LabelText({children}: {children: React.ReactNode}) { 364 + export function LabelText({ 365 + children, 366 + style, 367 + }: { 368 + children: React.ReactNode 369 + style?: StyleProp<TextStyle> 370 + }) { 359 371 const t = useTheme() 360 372 return ( 361 373 <Text 362 374 style={[ 363 375 a.font_bold, 364 - a.pt_md, 365 - a.pb_sm, 376 + a.p_sm, 366 377 t.atoms.text_contrast_low, 367 - { 368 - paddingHorizontal: 10, 369 - }, 378 + a.leading_snug, 379 + {paddingHorizontal: 10}, 380 + style, 370 381 ]}> 371 382 {children} 372 383 </Text>
+13 -2
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 48 48 useProfileMuteMutationQueue, 49 49 } from '#/state/queries/profile' 50 50 import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' 51 - import {useSession} from '#/state/session' 51 + import {useRequireAuth, useSession} from '#/state/session' 52 52 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 53 53 import * as Toast from '#/view/com/util/Toast' 54 54 import {useDialogControl} from '#/components/Dialog' ··· 113 113 const {mutateAsync: deletePostMutate} = usePostDeleteMutation() 114 114 const {mutateAsync: pinPostMutate, isPending: isPinPending} = 115 115 usePinnedPostMutation() 116 + const requireSignIn = useRequireAuth() 116 117 const hiddenPosts = useHiddenPosts() 117 118 const {hidePost} = useHiddenPostsApi() 118 119 const feedFeedback = useFeedFeedbackContext() ··· 397 398 openLink(url) 398 399 } 399 400 401 + const onSignIn = () => requireSignIn(() => {}) 402 + 400 403 const gate = useGate() 401 404 const isDiscoverDebugUser = 402 405 IS_INTERNAL || ··· 434 437 )} 435 438 436 439 <Menu.Group> 437 - {(!hideInPWI || hasSession) && ( 440 + {!hideInPWI || hasSession ? ( 438 441 <> 439 442 <Menu.Item 440 443 testID="postDropdownTranslateBtn" ··· 452 455 <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 453 456 </Menu.Item> 454 457 </> 458 + ) : ( 459 + <Menu.Item 460 + testID="postDropdownSignInBtn" 461 + label={_(msg`Sign in to view post`)} 462 + onPress={onSignIn}> 463 + <Menu.ItemText>{_(msg`Sign in to view post`)}</Menu.ItemText> 464 + <Menu.ItemIcon icon={Eye} position="right" /> 465 + </Menu.Item> 455 466 )} 456 467 </Menu.Group> 457 468
+15 -41
src/components/PostControls/ShareMenu/ShareMenuItems.tsx
··· 14 14 import {useProfileShadow} from '#/state/cache/profile-shadow' 15 15 import {useSession} from '#/state/session' 16 16 import * as Toast from '#/view/com/util/Toast' 17 + import {atoms as a} from '#/alf' 18 + import {Admonition} from '#/components/Admonition' 17 19 import {useDialogControl} from '#/components/Dialog' 18 20 import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' 19 21 import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox' ··· 21 23 import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 22 24 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane' 23 25 import * as Menu from '#/components/Menu' 24 - import * as Prompt from '#/components/Prompt' 25 26 import {useDevMode} from '#/storage/hooks/dev-mode' 26 27 import {RecentChats} from './RecentChats' 27 28 import {type ShareMenuItemsProps} from './ShareMenuItems.types' ··· 30 31 post, 31 32 onShare: onShareProp, 32 33 }: ShareMenuItemsProps): React.ReactNode => { 33 - const {hasSession, currentAccount} = useSession() 34 + const {hasSession} = useSession() 34 35 const {_} = useLingui() 35 36 const navigation = useNavigation<NavigationProp>() 36 - const pwiWarningShareControl = useDialogControl() 37 - const pwiWarningCopyControl = useDialogControl() 38 37 const sendViaChatControl = useDialogControl() 39 38 const [devModeEnabled] = useDevMode() 40 39 ··· 51 50 label => label.val === '!no-unauthenticated', 52 51 ) 53 52 }, [postAuthor]) 54 - 55 - const showLoggedOutWarning = 56 - postAuthor.did !== currentAccount?.did && hideInPWI 57 53 58 54 const onSharePost = () => { 59 55 logger.metric('share:press:nativeShare', {}, {statsig: true}) ··· 117 113 <Menu.Item 118 114 testID="postDropdownShareBtn" 119 115 label={_(msg`Share via...`)} 120 - onPress={() => { 121 - if (showLoggedOutWarning) { 122 - pwiWarningShareControl.open() 123 - } else { 124 - onSharePost() 125 - } 126 - }}> 116 + onPress={onSharePost}> 127 117 <Menu.ItemText> 128 118 <Trans>Share via...</Trans> 129 119 </Menu.ItemText> ··· 133 123 <Menu.Item 134 124 testID="postDropdownShareBtn" 135 125 label={_(msg`Copy link to post`)} 136 - onPress={() => { 137 - if (showLoggedOutWarning) { 138 - pwiWarningCopyControl.open() 139 - } else { 140 - onCopyLink() 141 - } 142 - }}> 126 + onPress={onCopyLink}> 143 127 <Menu.ItemText> 144 128 <Trans>Copy link to post</Trans> 145 129 </Menu.ItemText> ··· 147 131 </Menu.Item> 148 132 </Menu.Group> 149 133 134 + {hideInPWI && ( 135 + <Menu.Group> 136 + <Menu.ContainerItem> 137 + <Admonition type="warning" style={[a.flex_1, a.border_0, a.p_0]}> 138 + <Trans>This post is only visible to logged-in users.</Trans> 139 + </Admonition> 140 + </Menu.ContainerItem> 141 + </Menu.Group> 142 + )} 143 + 150 144 {devModeEnabled && ( 151 145 <Menu.Group> 152 146 <Menu.Item ··· 170 164 </Menu.Group> 171 165 )} 172 166 </Menu.Outer> 173 - 174 - <Prompt.Basic 175 - control={pwiWarningShareControl} 176 - title={_(msg`Note about sharing`)} 177 - description={_( 178 - msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`, 179 - )} 180 - onConfirm={onSharePost} 181 - confirmButtonCta={_(msg`Share anyway`)} 182 - /> 183 - 184 - <Prompt.Basic 185 - control={pwiWarningCopyControl} 186 - title={_(msg`Note about sharing`)} 187 - description={_( 188 - msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`, 189 - )} 190 - onConfirm={onCopyLink} 191 - confirmButtonCta={_(msg`Copy anyway`)} 192 - /> 193 167 194 168 <SendViaChatDialog 195 169 control={sendViaChatControl}
+61 -72
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 22 22 import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' 23 23 import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' 24 24 import * as Menu from '#/components/Menu' 25 - import * as Prompt from '#/components/Prompt' 26 25 import {useDevMode} from '#/storage/hooks/dev-mode' 27 26 import {type ShareMenuItemsProps} from './ShareMenuItems.types' 28 27 ··· 32 31 timestamp, 33 32 onShare: onShareProp, 34 33 }: ShareMenuItemsProps): React.ReactNode => { 35 - const {hasSession, currentAccount} = useSession() 34 + const {hasSession} = useSession() 36 35 const {gtMobile} = useBreakpoints() 37 36 const {_} = useLingui() 38 37 const navigation = useNavigation<NavigationProp>() 39 - const loggedOutWarningPromptControl = useDialogControl() 40 38 const embedPostControl = useDialogControl() 41 39 const sendViaChatControl = useDialogControl() 42 40 const [devModeEnabled] = useDevMode() ··· 56 54 ) 57 55 }, [postAuthor]) 58 56 59 - const showLoggedOutWarning = 60 - postAuthor.did !== currentAccount?.did && hideInPWI 61 - 62 57 const onCopyLink = () => { 63 58 logger.metric('share:press:copyLink', {}, {statsig: true}) 64 59 const url = toShareUrl(href) ··· 84 79 shareText(postAuthor.did) 85 80 } 86 81 82 + const copyLinkItem = ( 83 + <Menu.Item 84 + testID="postDropdownShareBtn" 85 + label={_(msg`Copy link to post`)} 86 + onPress={onCopyLink}> 87 + <Menu.ItemText> 88 + <Trans>Copy link to post</Trans> 89 + </Menu.ItemText> 90 + <Menu.ItemIcon icon={ChainLinkIcon} position="right" /> 91 + </Menu.Item> 92 + ) 93 + 87 94 return ( 88 95 <> 89 96 <Menu.Outer> 90 - <Menu.Group> 97 + {!hideInPWI && copyLinkItem} 98 + 99 + {hasSession && ( 91 100 <Menu.Item 92 - testID="postDropdownShareBtn" 93 - label={_(msg`Copy link to post`)} 101 + testID="postDropdownSendViaDMBtn" 102 + label={_(msg`Send via direct message`)} 94 103 onPress={() => { 95 - if (showLoggedOutWarning) { 96 - loggedOutWarningPromptControl.open() 97 - } else { 98 - onCopyLink() 99 - } 104 + logger.metric('share:press:openDmSearch', {}, {statsig: true}) 105 + sendViaChatControl.open() 100 106 }}> 101 107 <Menu.ItemText> 102 - <Trans>Copy link to post</Trans> 108 + <Trans>Send via direct message</Trans> 103 109 </Menu.ItemText> 104 - <Menu.ItemIcon icon={ChainLinkIcon} position="right" /> 110 + <Menu.ItemIcon icon={Send} position="right" /> 105 111 </Menu.Item> 112 + )} 106 113 107 - {hasSession && ( 114 + {canEmbed && ( 115 + <Menu.Item 116 + testID="postDropdownEmbedBtn" 117 + label={_(msg`Embed post`)} 118 + onPress={() => { 119 + logger.metric('share:press:embed', {}, {statsig: true}) 120 + embedPostControl.open() 121 + }}> 122 + <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> 123 + <Menu.ItemIcon icon={CodeBracketsIcon} position="right" /> 124 + </Menu.Item> 125 + )} 126 + 127 + {hideInPWI && ( 128 + <> 129 + {hasSession && <Menu.Divider />} 130 + {copyLinkItem} 131 + <Menu.LabelText style={{maxWidth: 220}}> 132 + <Trans>Note: This post is only visible to logged-in users.</Trans> 133 + </Menu.LabelText> 134 + </> 135 + )} 136 + 137 + {devModeEnabled && ( 138 + <> 139 + <Menu.Divider /> 108 140 <Menu.Item 109 - testID="postDropdownSendViaDMBtn" 110 - label={_(msg`Send via direct message`)} 111 - onPress={() => { 112 - logger.metric('share:press:openDmSearch', {}, {statsig: true}) 113 - sendViaChatControl.open() 114 - }}> 141 + testID="postAtUriShareBtn" 142 + label={_(msg`Copy post at:// URI`)} 143 + onPress={onShareATURI}> 115 144 <Menu.ItemText> 116 - <Trans>Send via direct message</Trans> 145 + <Trans>Copy post at:// URI</Trans> 117 146 </Menu.ItemText> 118 - <Menu.ItemIcon icon={Send} position="right" /> 147 + <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 119 148 </Menu.Item> 120 - )} 121 - 122 - {canEmbed && ( 123 149 <Menu.Item 124 - testID="postDropdownEmbedBtn" 125 - label={_(msg`Embed post`)} 126 - onPress={() => { 127 - logger.metric('share:press:embed', {}, {statsig: true}) 128 - embedPostControl.open() 129 - }}> 130 - <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> 131 - <Menu.ItemIcon icon={CodeBracketsIcon} position="right" /> 150 + testID="postAuthorDIDShareBtn" 151 + label={_(msg`Copy author DID`)} 152 + onPress={onShareAuthorDID}> 153 + <Menu.ItemText> 154 + <Trans>Copy author DID</Trans> 155 + </Menu.ItemText> 156 + <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 132 157 </Menu.Item> 133 - )} 134 - </Menu.Group> 135 - 136 - {devModeEnabled && ( 137 - <> 138 - <Menu.Divider /> 139 - <Menu.Group> 140 - <Menu.Item 141 - testID="postAtUriShareBtn" 142 - label={_(msg`Copy post at:// URI`)} 143 - onPress={onShareATURI}> 144 - <Menu.ItemText> 145 - <Trans>Copy post at:// URI</Trans> 146 - </Menu.ItemText> 147 - <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 148 - </Menu.Item> 149 - <Menu.Item 150 - testID="postAuthorDIDShareBtn" 151 - label={_(msg`Copy author DID`)} 152 - onPress={onShareAuthorDID}> 153 - <Menu.ItemText> 154 - <Trans>Copy author DID</Trans> 155 - </Menu.ItemText> 156 - <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 157 - </Menu.Item> 158 - </Menu.Group> 159 158 </> 160 159 )} 161 160 </Menu.Outer> 162 - 163 - <Prompt.Basic 164 - control={loggedOutWarningPromptControl} 165 - title={_(msg`Note about sharing`)} 166 - description={_( 167 - msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`, 168 - )} 169 - onConfirm={onCopyLink} 170 - confirmButtonCta={_(msg`Share anyway`)} 171 - /> 172 161 173 162 {canEmbed && ( 174 163 <EmbedDialog