Bluesky app fork with some witchin' additions 馃挮
at main 170 lines 5.4 kB view raw
1import React from 'react' 2import {type StyleProp, Text as RNText, type TextStyle} from 'react-native' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5import {useNavigation} from '@react-navigation/native' 6 7import {type NavigationProp} from '#/lib/routes/types' 8import {isInvalidHandle} from '#/lib/strings/handles' 9import { 10 usePreferencesQuery, 11 useRemoveMutedWordsMutation, 12 useUpsertMutedWordsMutation, 13} from '#/state/queries/preferences' 14import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' 15import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 16import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' 17import { 18 createStaticClick, 19 createStaticClickIfUnmodified, 20 InlineLinkText, 21} from '#/components/Link' 22import {Loader} from '#/components/Loader' 23import * as Menu from '#/components/Menu' 24import {IS_NATIVE, IS_WEB} from '#/env' 25 26export function RichTextTag({ 27 tag, 28 display, 29 authorHandle, 30 textStyle, 31}: { 32 tag: string 33 display: string 34 authorHandle?: string 35 textStyle: StyleProp<TextStyle> 36}) { 37 const {_} = useLingui() 38 const {isLoading: isPreferencesLoading, data: preferences} = 39 usePreferencesQuery() 40 const { 41 mutateAsync: upsertMutedWord, 42 variables: optimisticUpsert, 43 reset: resetUpsert, 44 } = useUpsertMutedWordsMutation() 45 const { 46 mutateAsync: removeMutedWords, 47 variables: optimisticRemove, 48 reset: resetRemove, 49 } = useRemoveMutedWordsMutation() 50 const navigation = useNavigation<NavigationProp>() 51 const isCashtag = tag.startsWith('$') 52 const label = isCashtag ? _(msg`Cashtag ${tag}`) : _(msg`Hashtag ${tag}`) 53 const hint = IS_NATIVE 54 ? _(msg`Long press to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 55 : _(msg`Click to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 56 57 const isMuted = Boolean( 58 (preferences?.moderationPrefs.mutedWords?.find( 59 m => m.value === tag && m.targets.includes('tag'), 60 ) ?? 61 optimisticUpsert?.find( 62 m => m.value === tag && m.targets.includes('tag'), 63 )) && 64 !optimisticRemove?.find(m => m?.value === tag), 65 ) 66 67 /* 68 * Mute word records that exactly match the tag in question. 69 */ 70 const removeableMuteWords = React.useMemo(() => { 71 return ( 72 preferences?.moderationPrefs.mutedWords?.filter(word => { 73 return word.value === tag 74 }) || [] 75 ) 76 }, [tag, preferences?.moderationPrefs?.mutedWords]) 77 78 return ( 79 <Menu.Root> 80 <Menu.Trigger label={label} hint={hint}> 81 {({props: menuProps}) => ( 82 <InlineLinkText 83 to={{ 84 screen: 'Hashtag', 85 params: {tag: encodeURIComponent(tag)}, 86 }} 87 {...menuProps} 88 onPress={e => { 89 if (IS_WEB) { 90 return createStaticClickIfUnmodified(() => { 91 if (!IS_NATIVE) { 92 menuProps.onPress() 93 } 94 }).onPress(e) 95 } 96 }} 97 onLongPress={createStaticClick(menuProps.onPress).onPress} 98 accessibilityHint={hint} 99 label={label} 100 style={textStyle} 101 emoji> 102 {IS_NATIVE ? ( 103 display 104 ) : ( 105 <RNText ref={menuProps.ref}>{display}</RNText> 106 )} 107 </InlineLinkText> 108 )} 109 </Menu.Trigger> 110 <Menu.Outer> 111 <Menu.Group> 112 <Menu.Item 113 label={_(msg`See ${isCashtag ? tag : `#${tag}`} posts`)} 114 onPress={() => { 115 navigation.push('Hashtag', { 116 tag: encodeURIComponent(tag), 117 }) 118 }}> 119 <Menu.ItemText> 120 {isCashtag ? ( 121 <Trans>See {tag} posts</Trans> 122 ) : ( 123 <Trans>See #{tag} posts</Trans> 124 )} 125 </Menu.ItemText> 126 <Menu.ItemIcon icon={Search} /> 127 </Menu.Item> 128 {authorHandle && !isInvalidHandle(authorHandle) && ( 129 <Menu.Item 130 label={_(msg`See ${isCashtag ? tag : `#${tag}`} posts by user`)} 131 onPress={() => { 132 navigation.push('Hashtag', { 133 tag: encodeURIComponent(tag), 134 author: authorHandle, 135 }) 136 }}> 137 <Menu.ItemText> 138 {isCashtag ? ( 139 <Trans>See {tag} posts by user</Trans> 140 ) : ( 141 <Trans>See #{tag} posts by user</Trans> 142 )} 143 </Menu.ItemText> 144 <Menu.ItemIcon icon={Person} /> 145 </Menu.Item> 146 )} 147 </Menu.Group> 148 <Menu.Divider /> 149 <Menu.Item 150 label={isMuted ? _(msg`Unmute ${tag}`) : _(msg`Mute ${tag}`)} 151 onPress={() => { 152 if (isMuted) { 153 resetUpsert() 154 removeMutedWords(removeableMuteWords) 155 } else { 156 resetRemove() 157 upsertMutedWord([ 158 {value: tag, targets: ['tag'], actorTarget: 'all'}, 159 ]) 160 } 161 }}> 162 <Menu.ItemText> 163 {isMuted ? _(msg`Unmute ${tag}`) : _(msg`Mute ${tag}`)} 164 </Menu.ItemText> 165 <Menu.ItemIcon icon={isPreferencesLoading ? Loader : Mute} /> 166 </Menu.Item> 167 </Menu.Outer> 168 </Menu.Root> 169 ) 170}