mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

at rm-patch-drawer 751 lines 26 kB view raw
1import React, {memo, useCallback} from 'react' 2import { 3 Platform, 4 type PressableProps, 5 type StyleProp, 6 type ViewStyle, 7} from 'react-native' 8import * as Clipboard from 'expo-clipboard' 9import { 10 AppBskyFeedDefs, 11 AppBskyFeedPost, 12 AppBskyFeedThreadgate, 13 AtUri, 14 RichText as RichTextAPI, 15} from '@atproto/api' 16import {msg, Trans} from '@lingui/macro' 17import {useLingui} from '@lingui/react' 18import {useNavigation} from '@react-navigation/native' 19 20import {useOpenLink} from '#/lib/hooks/useOpenLink' 21import {getCurrentRoute} from '#/lib/routes/helpers' 22import {makeProfileLink} from '#/lib/routes/links' 23import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' 24import {shareUrl} from '#/lib/sharing' 25import {logEvent} from '#/lib/statsig/statsig' 26import {richTextToString} from '#/lib/strings/rich-text-helpers' 27import {toShareUrl} from '#/lib/strings/url-helpers' 28import {getTranslatorLink} from '#/locale/helpers' 29import {logger} from '#/logger' 30import {isWeb} from '#/platform/detection' 31import {Shadow} from '#/state/cache/post-shadow' 32import {useProfileShadow} from '#/state/cache/profile-shadow' 33import {useFeedFeedbackContext} from '#/state/feed-feedback' 34import {useLanguagePrefs} from '#/state/preferences' 35import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' 36import {usePinnedPostMutation} from '#/state/queries/pinned-post' 37import { 38 usePostDeleteMutation, 39 useThreadMuteMutationQueue, 40} from '#/state/queries/post' 41import {useToggleQuoteDetachmentMutation} from '#/state/queries/postgate' 42import {getMaybeDetachedQuoteEmbed} from '#/state/queries/postgate/util' 43import {useProfileBlockMutationQueue} from '#/state/queries/profile' 44import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' 45import {useSession} from '#/state/session' 46import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 47import {useBreakpoints} from '#/alf' 48import {useDialogControl} from '#/components/Dialog' 49import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 50import {EmbedDialog} from '#/components/dialogs/Embed' 51import { 52 PostInteractionSettingsDialog, 53 usePrefetchPostInteractionSettings, 54} from '#/components/dialogs/PostInteractionSettingsDialog' 55import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' 56import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' 57import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' 58import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 59import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets' 60import { 61 EmojiSad_Stroke2_Corner0_Rounded as EmojiSad, 62 EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmile, 63} from '#/components/icons/Emoji' 64import {Eye_Stroke2_Corner0_Rounded as Eye} from '#/components/icons/Eye' 65import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 66import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' 67import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 68import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' 69import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Person' 70import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 71import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' 72import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 73import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' 74import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' 75import {Loader} from '#/components/Loader' 76import * as Menu from '#/components/Menu' 77import * as Prompt from '#/components/Prompt' 78import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' 79import * as Toast from '../Toast' 80 81let PostDropdownMenuItems = ({ 82 post, 83 postFeedContext, 84 record, 85 richText, 86 timestamp, 87 threadgateRecord, 88}: { 89 testID: string 90 post: Shadow<AppBskyFeedDefs.PostView> 91 postFeedContext: string | undefined 92 record: AppBskyFeedPost.Record 93 richText: RichTextAPI 94 style?: StyleProp<ViewStyle> 95 hitSlop?: PressableProps['hitSlop'] 96 size?: 'lg' | 'md' | 'sm' 97 timestamp: string 98 threadgateRecord?: AppBskyFeedThreadgate.Record 99}): React.ReactNode => { 100 const {hasSession, currentAccount} = useSession() 101 const {gtMobile} = useBreakpoints() 102 const {_} = useLingui() 103 const langPrefs = useLanguagePrefs() 104 const {mutateAsync: deletePostMutate} = usePostDeleteMutation() 105 const {mutateAsync: pinPostMutate, isPending: isPinPending} = 106 usePinnedPostMutation() 107 const hiddenPosts = useHiddenPosts() 108 const {hidePost} = useHiddenPostsApi() 109 const feedFeedback = useFeedFeedbackContext() 110 const openLink = useOpenLink() 111 const navigation = useNavigation<NavigationProp>() 112 const {mutedWordsDialogControl} = useGlobalDialogsControlContext() 113 const blockPromptControl = useDialogControl() 114 const reportDialogControl = useReportDialogControl() 115 const deletePromptControl = useDialogControl() 116 const hidePromptControl = useDialogControl() 117 const loggedOutWarningPromptControl = useDialogControl() 118 const embedPostControl = useDialogControl() 119 const sendViaChatControl = useDialogControl() 120 const postInteractionSettingsDialogControl = useDialogControl() 121 const quotePostDetachConfirmControl = useDialogControl() 122 const hideReplyConfirmControl = useDialogControl() 123 const {mutateAsync: toggleReplyVisibility} = 124 useToggleReplyVisibilityMutation() 125 126 const postUri = post.uri 127 const postCid = post.cid 128 const postAuthor = useProfileShadow(post.author) 129 const quoteEmbed = React.useMemo(() => { 130 if (!currentAccount || !post.embed) return 131 return getMaybeDetachedQuoteEmbed({ 132 viewerDid: currentAccount.did, 133 post, 134 }) 135 }, [post, currentAccount]) 136 137 const rootUri = record.reply?.root?.uri || postUri 138 const isReply = Boolean(record.reply) 139 const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue( 140 post, 141 rootUri, 142 ) 143 const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri) 144 const isAuthor = postAuthor.did === currentAccount?.did 145 const isRootPostAuthor = new AtUri(rootUri).host === currentAccount?.did 146 const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({ 147 threadgateRecord, 148 }) 149 const isReplyHiddenByThreadgate = threadgateHiddenReplies.has(postUri) 150 const isPinned = post.viewer?.pinned 151 152 const {mutateAsync: toggleQuoteDetachment, isPending: isDetachPending} = 153 useToggleQuoteDetachmentMutation() 154 155 const [queueBlock] = useProfileBlockMutationQueue(postAuthor) 156 157 const prefetchPostInteractionSettings = usePrefetchPostInteractionSettings({ 158 postUri: post.uri, 159 rootPostUri: rootUri, 160 }) 161 162 const href = React.useMemo(() => { 163 const urip = new AtUri(postUri) 164 return makeProfileLink(postAuthor, 'post', urip.rkey) 165 }, [postUri, postAuthor]) 166 167 const translatorUrl = getTranslatorLink( 168 record.text, 169 langPrefs.primaryLanguage, 170 ) 171 172 const onDeletePost = React.useCallback(() => { 173 deletePostMutate({uri: postUri}).then( 174 () => { 175 Toast.show(_(msg`Post deleted`)) 176 177 const route = getCurrentRoute(navigation.getState()) 178 if (route.name === 'PostThread') { 179 const params = route.params as CommonNavigatorParams['PostThread'] 180 if ( 181 currentAccount && 182 isAuthor && 183 (params.name === currentAccount.handle || 184 params.name === currentAccount.did) 185 ) { 186 const currentHref = makeProfileLink(postAuthor, 'post', params.rkey) 187 if (currentHref === href && navigation.canGoBack()) { 188 navigation.goBack() 189 } 190 } 191 } 192 }, 193 e => { 194 logger.error('Failed to delete post', {message: e}) 195 Toast.show(_(msg`Failed to delete post, please try again`), 'xmark') 196 }, 197 ) 198 }, [ 199 navigation, 200 postUri, 201 deletePostMutate, 202 postAuthor, 203 currentAccount, 204 isAuthor, 205 href, 206 _, 207 ]) 208 209 const onToggleThreadMute = React.useCallback(() => { 210 try { 211 if (isThreadMuted) { 212 unmuteThread() 213 Toast.show(_(msg`You will now receive notifications for this thread`)) 214 } else { 215 muteThread() 216 Toast.show( 217 _(msg`You will no longer receive notifications for this thread`), 218 ) 219 } 220 } catch (e: any) { 221 if (e?.name !== 'AbortError') { 222 logger.error('Failed to toggle thread mute', {message: e}) 223 Toast.show( 224 _(msg`Failed to toggle thread mute, please try again`), 225 'xmark', 226 ) 227 } 228 } 229 }, [isThreadMuted, unmuteThread, _, muteThread]) 230 231 const onCopyPostText = React.useCallback(() => { 232 const str = richTextToString(richText, true) 233 234 Clipboard.setStringAsync(str) 235 Toast.show(_(msg`Copied to clipboard`), 'clipboard-check') 236 }, [_, richText]) 237 238 const onPressTranslate = React.useCallback(async () => { 239 await openLink(translatorUrl, true) 240 }, [openLink, translatorUrl]) 241 242 const onHidePost = React.useCallback(() => { 243 hidePost({uri: postUri}) 244 }, [postUri, hidePost]) 245 246 const hideInPWI = React.useMemo(() => { 247 return !!postAuthor.labels?.find( 248 label => label.val === '!no-unauthenticated', 249 ) 250 }, [postAuthor]) 251 252 const showLoggedOutWarning = 253 postAuthor.did !== currentAccount?.did && hideInPWI 254 255 const onSharePost = React.useCallback(() => { 256 const url = toShareUrl(href) 257 shareUrl(url) 258 }, [href]) 259 260 const onPressShowMore = React.useCallback(() => { 261 feedFeedback.sendInteraction({ 262 event: 'app.bsky.feed.defs#requestMore', 263 item: postUri, 264 feedContext: postFeedContext, 265 }) 266 Toast.show(_(msg`Feedback sent!`)) 267 }, [feedFeedback, postUri, postFeedContext, _]) 268 269 const onPressShowLess = React.useCallback(() => { 270 feedFeedback.sendInteraction({ 271 event: 'app.bsky.feed.defs#requestLess', 272 item: postUri, 273 feedContext: postFeedContext, 274 }) 275 Toast.show(_(msg`Feedback sent!`)) 276 }, [feedFeedback, postUri, postFeedContext, _]) 277 278 const onSelectChatToShareTo = React.useCallback( 279 (conversation: string) => { 280 navigation.navigate('MessagesConversation', { 281 conversation, 282 embed: postUri, 283 }) 284 }, 285 [navigation, postUri], 286 ) 287 288 const onToggleQuotePostAttachment = React.useCallback(async () => { 289 if (!quoteEmbed) return 290 291 const action = quoteEmbed.isDetached ? 'reattach' : 'detach' 292 const isDetach = action === 'detach' 293 294 try { 295 await toggleQuoteDetachment({ 296 post, 297 quoteUri: quoteEmbed.uri, 298 action: quoteEmbed.isDetached ? 'reattach' : 'detach', 299 }) 300 Toast.show( 301 isDetach 302 ? _(msg`Quote post was successfully detached`) 303 : _(msg`Quote post was re-attached`), 304 ) 305 } catch (e: any) { 306 Toast.show(_(msg`Updating quote attachment failed`)) 307 logger.error(`Failed to ${action} quote`, {safeMessage: e.message}) 308 } 309 }, [_, quoteEmbed, post, toggleQuoteDetachment]) 310 311 const canHidePostForMe = !isAuthor && !isPostHidden 312 const canEmbed = isWeb && gtMobile && !hideInPWI 313 const canHideReplyForEveryone = 314 !isAuthor && isRootPostAuthor && !isPostHidden && isReply 315 const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer 316 317 const onToggleReplyVisibility = React.useCallback(async () => { 318 // TODO no threadgate? 319 if (!canHideReplyForEveryone) return 320 321 const action = isReplyHiddenByThreadgate ? 'show' : 'hide' 322 const isHide = action === 'hide' 323 324 try { 325 await toggleReplyVisibility({ 326 postUri: rootUri, 327 replyUri: postUri, 328 action, 329 }) 330 Toast.show( 331 isHide 332 ? _(msg`Reply was successfully hidden`) 333 : _(msg`Reply visibility updated`), 334 ) 335 } catch (e: any) { 336 Toast.show(_(msg`Updating reply visibility failed`)) 337 logger.error(`Failed to ${action} reply`, {safeMessage: e.message}) 338 } 339 }, [ 340 _, 341 isReplyHiddenByThreadgate, 342 rootUri, 343 postUri, 344 canHideReplyForEveryone, 345 toggleReplyVisibility, 346 ]) 347 348 const onPressPin = useCallback(() => { 349 logEvent(isPinned ? 'post:unpin' : 'post:pin', {}) 350 pinPostMutate({ 351 postUri, 352 postCid, 353 action: isPinned ? 'unpin' : 'pin', 354 }) 355 }, [isPinned, pinPostMutate, postCid, postUri]) 356 357 const onBlockAuthor = useCallback(async () => { 358 try { 359 await queueBlock() 360 Toast.show(_(msg`Account blocked`)) 361 } catch (e: any) { 362 if (e?.name !== 'AbortError') { 363 logger.error('Failed to block account', {message: e}) 364 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 365 } 366 } 367 }, [_, queueBlock]) 368 369 return ( 370 <> 371 <Menu.Outer> 372 {isAuthor && ( 373 <> 374 <Menu.Group> 375 <Menu.Item 376 testID="pinPostBtn" 377 label={ 378 isPinned 379 ? _(msg`Unpin from profile`) 380 : _(msg`Pin to your profile`) 381 } 382 disabled={isPinPending} 383 onPress={onPressPin}> 384 <Menu.ItemText> 385 {isPinned 386 ? _(msg`Unpin from profile`) 387 : _(msg`Pin to your profile`)} 388 </Menu.ItemText> 389 <Menu.ItemIcon 390 icon={isPinPending ? Loader : PinIcon} 391 position="right" 392 /> 393 </Menu.Item> 394 </Menu.Group> 395 <Menu.Divider /> 396 </> 397 )} 398 399 <Menu.Group> 400 {(!hideInPWI || hasSession) && ( 401 <> 402 <Menu.Item 403 testID="postDropdownTranslateBtn" 404 label={_(msg`Translate`)} 405 onPress={onPressTranslate}> 406 <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText> 407 <Menu.ItemIcon icon={Translate} position="right" /> 408 </Menu.Item> 409 410 <Menu.Item 411 testID="postDropdownCopyTextBtn" 412 label={_(msg`Copy post text`)} 413 onPress={onCopyPostText}> 414 <Menu.ItemText>{_(msg`Copy post text`)}</Menu.ItemText> 415 <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 416 </Menu.Item> 417 </> 418 )} 419 420 {hasSession && ( 421 <Menu.Item 422 testID="postDropdownSendViaDMBtn" 423 label={_(msg`Send via direct message`)} 424 onPress={() => sendViaChatControl.open()}> 425 <Menu.ItemText> 426 <Trans>Send via direct message</Trans> 427 </Menu.ItemText> 428 <Menu.ItemIcon icon={Send} position="right" /> 429 </Menu.Item> 430 )} 431 432 <Menu.Item 433 testID="postDropdownShareBtn" 434 label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} 435 onPress={() => { 436 if (showLoggedOutWarning) { 437 loggedOutWarningPromptControl.open() 438 } else { 439 onSharePost() 440 } 441 }}> 442 <Menu.ItemText> 443 {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} 444 </Menu.ItemText> 445 <Menu.ItemIcon icon={Share} position="right" /> 446 </Menu.Item> 447 448 {canEmbed && ( 449 <Menu.Item 450 testID="postDropdownEmbedBtn" 451 label={_(msg`Embed post`)} 452 onPress={() => embedPostControl.open()}> 453 <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> 454 <Menu.ItemIcon icon={CodeBrackets} position="right" /> 455 </Menu.Item> 456 )} 457 </Menu.Group> 458 459 {hasSession && feedFeedback.enabled && ( 460 <> 461 <Menu.Divider /> 462 <Menu.Group> 463 <Menu.Item 464 testID="postDropdownShowMoreBtn" 465 label={_(msg`Show more like this`)} 466 onPress={onPressShowMore}> 467 <Menu.ItemText>{_(msg`Show more like this`)}</Menu.ItemText> 468 <Menu.ItemIcon icon={EmojiSmile} position="right" /> 469 </Menu.Item> 470 471 <Menu.Item 472 testID="postDropdownShowLessBtn" 473 label={_(msg`Show less like this`)} 474 onPress={onPressShowLess}> 475 <Menu.ItemText>{_(msg`Show less like this`)}</Menu.ItemText> 476 <Menu.ItemIcon icon={EmojiSad} position="right" /> 477 </Menu.Item> 478 </Menu.Group> 479 </> 480 )} 481 482 {hasSession && ( 483 <> 484 <Menu.Divider /> 485 <Menu.Group> 486 <Menu.Item 487 testID="postDropdownMuteThreadBtn" 488 label={ 489 isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`) 490 } 491 onPress={onToggleThreadMute}> 492 <Menu.ItemText> 493 {isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`)} 494 </Menu.ItemText> 495 <Menu.ItemIcon 496 icon={isThreadMuted ? Unmute : Mute} 497 position="right" 498 /> 499 </Menu.Item> 500 501 <Menu.Item 502 testID="postDropdownMuteWordsBtn" 503 label={_(msg`Mute words & tags`)} 504 onPress={() => mutedWordsDialogControl.open()}> 505 <Menu.ItemText>{_(msg`Mute words & tags`)}</Menu.ItemText> 506 <Menu.ItemIcon icon={Filter} position="right" /> 507 </Menu.Item> 508 </Menu.Group> 509 </> 510 )} 511 512 {hasSession && 513 (canHideReplyForEveryone || canDetachQuote || canHidePostForMe) && ( 514 <> 515 <Menu.Divider /> 516 <Menu.Group> 517 {canHidePostForMe && ( 518 <Menu.Item 519 testID="postDropdownHideBtn" 520 label={ 521 isReply 522 ? _(msg`Hide reply for me`) 523 : _(msg`Hide post for me`) 524 } 525 onPress={() => hidePromptControl.open()}> 526 <Menu.ItemText> 527 {isReply 528 ? _(msg`Hide reply for me`) 529 : _(msg`Hide post for me`)} 530 </Menu.ItemText> 531 <Menu.ItemIcon icon={EyeSlash} position="right" /> 532 </Menu.Item> 533 )} 534 {canHideReplyForEveryone && ( 535 <Menu.Item 536 testID="postDropdownHideBtn" 537 label={ 538 isReplyHiddenByThreadgate 539 ? _(msg`Show reply for everyone`) 540 : _(msg`Hide reply for everyone`) 541 } 542 onPress={ 543 isReplyHiddenByThreadgate 544 ? onToggleReplyVisibility 545 : () => hideReplyConfirmControl.open() 546 }> 547 <Menu.ItemText> 548 {isReplyHiddenByThreadgate 549 ? _(msg`Show reply for everyone`) 550 : _(msg`Hide reply for everyone`)} 551 </Menu.ItemText> 552 <Menu.ItemIcon 553 icon={isReplyHiddenByThreadgate ? Eye : EyeSlash} 554 position="right" 555 /> 556 </Menu.Item> 557 )} 558 559 {canDetachQuote && ( 560 <Menu.Item 561 disabled={isDetachPending} 562 testID="postDropdownHideBtn" 563 label={ 564 quoteEmbed.isDetached 565 ? _(msg`Re-attach quote`) 566 : _(msg`Detach quote`) 567 } 568 onPress={ 569 quoteEmbed.isDetached 570 ? onToggleQuotePostAttachment 571 : () => quotePostDetachConfirmControl.open() 572 }> 573 <Menu.ItemText> 574 {quoteEmbed.isDetached 575 ? _(msg`Re-attach quote`) 576 : _(msg`Detach quote`)} 577 </Menu.ItemText> 578 <Menu.ItemIcon 579 icon={ 580 isDetachPending 581 ? Loader 582 : quoteEmbed.isDetached 583 ? Eye 584 : EyeSlash 585 } 586 position="right" 587 /> 588 </Menu.Item> 589 )} 590 </Menu.Group> 591 </> 592 )} 593 594 {hasSession && ( 595 <> 596 <Menu.Divider /> 597 <Menu.Group> 598 {!isAuthor && ( 599 <> 600 {!postAuthor.viewer?.blocking && ( 601 <Menu.Item 602 testID="postDropdownBlockBtn" 603 label={_(msg`Block account`)} 604 onPress={() => blockPromptControl.open()}> 605 <Menu.ItemText>{_(msg`Block account`)}</Menu.ItemText> 606 <Menu.ItemIcon icon={PersonX} position="right" /> 607 </Menu.Item> 608 )} 609 <Menu.Item 610 testID="postDropdownReportBtn" 611 label={_(msg`Report post`)} 612 onPress={() => reportDialogControl.open()}> 613 <Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText> 614 <Menu.ItemIcon icon={Warning} position="right" /> 615 </Menu.Item> 616 </> 617 )} 618 619 {isAuthor && ( 620 <> 621 <Menu.Item 622 testID="postDropdownEditPostInteractions" 623 label={_(msg`Edit interaction settings`)} 624 onPress={() => postInteractionSettingsDialogControl.open()} 625 {...(isAuthor 626 ? Platform.select({ 627 web: { 628 onHoverIn: prefetchPostInteractionSettings, 629 }, 630 native: { 631 onPressIn: prefetchPostInteractionSettings, 632 }, 633 }) 634 : {})}> 635 <Menu.ItemText> 636 {_(msg`Edit interaction settings`)} 637 </Menu.ItemText> 638 <Menu.ItemIcon icon={Gear} position="right" /> 639 </Menu.Item> 640 <Menu.Item 641 testID="postDropdownDeleteBtn" 642 label={_(msg`Delete post`)} 643 onPress={() => deletePromptControl.open()}> 644 <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText> 645 <Menu.ItemIcon icon={Trash} position="right" /> 646 </Menu.Item> 647 </> 648 )} 649 </Menu.Group> 650 </> 651 )} 652 </Menu.Outer> 653 654 <Prompt.Basic 655 control={deletePromptControl} 656 title={_(msg`Delete this post?`)} 657 description={_( 658 msg`If you remove this post, you won't be able to recover it.`, 659 )} 660 onConfirm={onDeletePost} 661 confirmButtonCta={_(msg`Delete`)} 662 confirmButtonColor="negative" 663 /> 664 665 <Prompt.Basic 666 control={hidePromptControl} 667 title={isReply ? _(msg`Hide this reply?`) : _(msg`Hide this post?`)} 668 description={_( 669 msg`This post will be hidden from feeds and threads. This cannot be undone.`, 670 )} 671 onConfirm={onHidePost} 672 confirmButtonCta={_(msg`Hide`)} 673 /> 674 675 <ReportDialog 676 control={reportDialogControl} 677 params={{ 678 type: 'post', 679 uri: postUri, 680 cid: postCid, 681 }} 682 /> 683 684 <Prompt.Basic 685 control={loggedOutWarningPromptControl} 686 title={_(msg`Note about sharing`)} 687 description={_( 688 msg`This post is only visible to logged-in users. It won't be visible to people who aren't logged in.`, 689 )} 690 onConfirm={onSharePost} 691 confirmButtonCta={_(msg`Share anyway`)} 692 /> 693 694 {canEmbed && ( 695 <EmbedDialog 696 control={embedPostControl} 697 postCid={postCid} 698 postUri={postUri} 699 record={record} 700 postAuthor={postAuthor} 701 timestamp={timestamp} 702 /> 703 )} 704 705 <SendViaChatDialog 706 control={sendViaChatControl} 707 onSelectChat={onSelectChatToShareTo} 708 /> 709 710 <PostInteractionSettingsDialog 711 control={postInteractionSettingsDialogControl} 712 postUri={post.uri} 713 rootPostUri={rootUri} 714 initialThreadgateView={post.threadgate} 715 /> 716 717 <Prompt.Basic 718 control={quotePostDetachConfirmControl} 719 title={_(msg`Detach quote post?`)} 720 description={_( 721 msg`This will remove your post from this quote post for all users, and replace it with a placeholder.`, 722 )} 723 onConfirm={onToggleQuotePostAttachment} 724 confirmButtonCta={_(msg`Yes, detach`)} 725 /> 726 727 <Prompt.Basic 728 control={hideReplyConfirmControl} 729 title={_(msg`Hide this reply?`)} 730 description={_( 731 msg`This reply will be sorted into a hidden section at the bottom of your thread and will mute notifications for subsequent replies - both for yourself and others.`, 732 )} 733 onConfirm={onToggleReplyVisibility} 734 confirmButtonCta={_(msg`Yes, hide`)} 735 /> 736 737 <Prompt.Basic 738 control={blockPromptControl} 739 title={_(msg`Block Account?`)} 740 description={_( 741 msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 742 )} 743 onConfirm={onBlockAuthor} 744 confirmButtonCta={_(msg`Block`)} 745 confirmButtonColor="negative" 746 /> 747 </> 748 ) 749} 750PostDropdownMenuItems = memo(PostDropdownMenuItems) 751export {PostDropdownMenuItems}