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