mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at tooltip 197 lines 6.9 kB view raw
1import {memo, useCallback} from 'react' 2import {LayoutAnimation} from 'react-native' 3import * as Clipboard from 'expo-clipboard' 4import {type ChatBskyConvoDefs, RichText} from '@atproto/api' 5import {msg} from '@lingui/macro' 6import {useLingui} from '@lingui/react' 7 8import {useOpenLink} from '#/lib/hooks/useOpenLink' 9import {richTextToString} from '#/lib/strings/rich-text-helpers' 10import {getTranslatorLink} from '#/locale/helpers' 11import {logger} from '#/logger' 12import {isNative} from '#/platform/detection' 13import {useConvoActive} from '#/state/messages/convo' 14import {useLanguagePrefs} from '#/state/preferences' 15import {useSession} from '#/state/session' 16import * as Toast from '#/view/com/util/Toast' 17import * as ContextMenu from '#/components/ContextMenu' 18import {type TriggerProps} from '#/components/ContextMenu/types' 19import {ReportDialog} from '#/components/dms/ReportDialog' 20import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' 21import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 22import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' 23import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' 24import * as Prompt from '#/components/Prompt' 25import {usePromptControl} from '#/components/Prompt' 26import {EmojiReactionPicker} from './EmojiReactionPicker' 27import {hasReachedReactionLimit} from './util' 28 29export let MessageContextMenu = ({ 30 message, 31 children, 32}: { 33 message: ChatBskyConvoDefs.MessageView 34 children: TriggerProps['children'] 35}): React.ReactNode => { 36 const {_} = useLingui() 37 const {currentAccount} = useSession() 38 const convo = useConvoActive() 39 const deleteControl = usePromptControl() 40 const reportControl = usePromptControl() 41 const langPrefs = useLanguagePrefs() 42 const openLink = useOpenLink() 43 44 const isFromSelf = message.sender?.did === currentAccount?.did 45 46 const onCopyMessage = useCallback(() => { 47 const str = richTextToString( 48 new RichText({ 49 text: message.text, 50 facets: message.facets, 51 }), 52 true, 53 ) 54 55 Clipboard.setStringAsync(str) 56 Toast.show(_(msg`Copied to clipboard`), 'clipboard-check') 57 }, [_, message.text, message.facets]) 58 59 const onPressTranslateMessage = useCallback(() => { 60 const translatorUrl = getTranslatorLink( 61 message.text, 62 langPrefs.primaryLanguage, 63 ) 64 openLink(translatorUrl, true) 65 66 logger.metric( 67 'translate', 68 { 69 sourceLanguages: [], 70 targetLanguage: langPrefs.primaryLanguage, 71 textLength: message.text.length, 72 }, 73 {statsig: false}, 74 ) 75 }, [langPrefs.primaryLanguage, message.text, openLink]) 76 77 const onDelete = useCallback(() => { 78 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 79 convo 80 .deleteMessage(message.id) 81 .then(() => 82 Toast.show(_(msg({message: 'Message deleted', context: 'toast'}))), 83 ) 84 .catch(() => Toast.show(_(msg`Failed to delete message`))) 85 }, [_, convo, message.id]) 86 87 const onEmojiSelect = useCallback( 88 (emoji: string) => { 89 if ( 90 message.reactions?.find( 91 reaction => 92 reaction.value === emoji && 93 reaction.sender.did === currentAccount?.did, 94 ) 95 ) { 96 convo 97 .removeReaction(message.id, emoji) 98 .catch(() => Toast.show(_(msg`Failed to remove emoji reaction`))) 99 } else { 100 if (hasReachedReactionLimit(message, currentAccount?.did)) return 101 convo 102 .addReaction(message.id, emoji) 103 .catch(() => 104 Toast.show(_(msg`Failed to add emoji reaction`), 'xmark'), 105 ) 106 } 107 }, 108 [_, convo, message, currentAccount?.did], 109 ) 110 111 const sender = convo.convo.members.find( 112 member => member.did === message.sender.did, 113 ) 114 115 return ( 116 <> 117 <ContextMenu.Root> 118 {isNative && ( 119 <ContextMenu.AuxiliaryView align={isFromSelf ? 'right' : 'left'}> 120 <EmojiReactionPicker 121 message={message} 122 onEmojiSelect={onEmojiSelect} 123 /> 124 </ContextMenu.AuxiliaryView> 125 )} 126 127 <ContextMenu.Trigger 128 label={_(msg`Message options`)} 129 contentLabel={_( 130 msg`Message from @${ 131 sender?.handle ?? 'unknown' // should always be defined 132 }: ${message.text}`, 133 )}> 134 {children} 135 </ContextMenu.Trigger> 136 137 <ContextMenu.Outer align={isFromSelf ? 'right' : 'left'}> 138 {message.text.length > 0 && ( 139 <> 140 <ContextMenu.Item 141 testID="messageDropdownTranslateBtn" 142 label={_(msg`Translate`)} 143 onPress={onPressTranslateMessage}> 144 <ContextMenu.ItemText>{_(msg`Translate`)}</ContextMenu.ItemText> 145 <ContextMenu.ItemIcon icon={Translate} position="right" /> 146 </ContextMenu.Item> 147 <ContextMenu.Item 148 testID="messageDropdownCopyBtn" 149 label={_(msg`Copy message text`)} 150 onPress={onCopyMessage}> 151 <ContextMenu.ItemText> 152 {_(msg`Copy message text`)} 153 </ContextMenu.ItemText> 154 <ContextMenu.ItemIcon icon={ClipboardIcon} position="right" /> 155 </ContextMenu.Item> 156 <ContextMenu.Divider /> 157 </> 158 )} 159 <ContextMenu.Item 160 testID="messageDropdownDeleteBtn" 161 label={_(msg`Delete message for me`)} 162 onPress={() => deleteControl.open()}> 163 <ContextMenu.ItemText>{_(msg`Delete for me`)}</ContextMenu.ItemText> 164 <ContextMenu.ItemIcon icon={Trash} position="right" /> 165 </ContextMenu.Item> 166 {!isFromSelf && ( 167 <ContextMenu.Item 168 testID="messageDropdownReportBtn" 169 label={_(msg`Report message`)} 170 onPress={() => reportControl.open()}> 171 <ContextMenu.ItemText>{_(msg`Report`)}</ContextMenu.ItemText> 172 <ContextMenu.ItemIcon icon={Warning} position="right" /> 173 </ContextMenu.Item> 174 )} 175 </ContextMenu.Outer> 176 </ContextMenu.Root> 177 178 <ReportDialog 179 currentScreen="conversation" 180 params={{type: 'convoMessage', convoId: convo.convo.id, message}} 181 control={reportControl} 182 /> 183 184 <Prompt.Basic 185 control={deleteControl} 186 title={_(msg`Delete message`)} 187 description={_( 188 msg`Are you sure you want to delete this message? The message will be deleted for you, but not for the other participant.`, 189 )} 190 confirmButtonCta={_(msg`Delete`)} 191 confirmButtonColor="negative" 192 onConfirm={onDelete} 193 /> 194 </> 195 ) 196} 197MessageContextMenu = memo(MessageContextMenu)