mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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}