my fork of the bluesky client
at main 163 lines 4.4 kB view raw
1import React from 'react' 2import {msg} from '@lingui/macro' 3import {useLingui} from '@lingui/react' 4import {useNavigation} from '@react-navigation/native' 5 6import {NavigationProp} from '#/lib/routes/types' 7import {isInvalidHandle} from '#/lib/strings/handles' 8import {enforceLen} from '#/lib/strings/helpers' 9import { 10 usePreferencesQuery, 11 useRemoveMutedWordsMutation, 12 useUpsertMutedWordsMutation, 13} from '#/state/queries/preferences' 14import {EventStopper} from '#/view/com/util/EventStopper' 15import {NativeDropdown} from '#/view/com/util/forms/NativeDropdown' 16import {web} from '#/alf' 17import * as Dialog from '#/components/Dialog' 18 19export function useTagMenuControl(): Dialog.DialogControlProps { 20 return { 21 id: '', 22 // @ts-ignore 23 ref: null, 24 open: () => { 25 throw new Error(`TagMenu controls are only available on native platforms`) 26 }, 27 close: () => { 28 throw new Error(`TagMenu controls are only available on native platforms`) 29 }, 30 } 31} 32 33export function TagMenu({ 34 children, 35 tag, 36 authorHandle, 37}: React.PropsWithChildren<{ 38 /** 39 * This should be the sanitized tag value from the facet itself, not the 40 * "display" value with a leading `#`. 41 */ 42 tag: string 43 authorHandle?: string 44}>) { 45 const {_} = useLingui() 46 const navigation = useNavigation<NavigationProp>() 47 const {data: preferences} = usePreferencesQuery() 48 const {mutateAsync: upsertMutedWord, variables: optimisticUpsert} = 49 useUpsertMutedWordsMutation() 50 const {mutateAsync: removeMutedWords, variables: optimisticRemove} = 51 useRemoveMutedWordsMutation() 52 const isMuted = Boolean( 53 (preferences?.moderationPrefs.mutedWords?.find( 54 m => m.value === tag && m.targets.includes('tag'), 55 ) ?? 56 optimisticUpsert?.find( 57 m => m.value === tag && m.targets.includes('tag'), 58 )) && 59 !optimisticRemove?.find(m => m?.value === tag), 60 ) 61 const truncatedTag = '#' + enforceLen(tag, 15, true, 'middle') 62 63 /* 64 * Mute word records that exactly match the tag in question. 65 */ 66 const removeableMuteWords = React.useMemo(() => { 67 return ( 68 preferences?.moderationPrefs.mutedWords?.filter(word => { 69 return word.value === tag 70 }) || [] 71 ) 72 }, [tag, preferences?.moderationPrefs?.mutedWords]) 73 74 const dropdownItems = React.useMemo(() => { 75 return [ 76 { 77 label: _(msg`See ${truncatedTag} posts`), 78 onPress() { 79 navigation.push('Hashtag', { 80 tag: encodeURIComponent(tag), 81 }) 82 }, 83 testID: 'tagMenuSearch', 84 icon: { 85 ios: { 86 name: 'magnifyingglass', 87 }, 88 android: '', 89 web: 'magnifying-glass', 90 }, 91 }, 92 authorHandle && 93 !isInvalidHandle(authorHandle) && { 94 label: _(msg`See ${truncatedTag} posts by user`), 95 onPress() { 96 navigation.push('Hashtag', { 97 tag: encodeURIComponent(tag), 98 author: authorHandle, 99 }) 100 }, 101 testID: 'tagMenuSearchByUser', 102 icon: { 103 ios: { 104 name: 'magnifyingglass', 105 }, 106 android: '', 107 web: ['far', 'user'], 108 }, 109 }, 110 preferences && { 111 label: 'separator', 112 }, 113 preferences && { 114 label: isMuted 115 ? _(msg`Unmute ${truncatedTag}`) 116 : _(msg`Mute ${truncatedTag}`), 117 onPress() { 118 if (isMuted) { 119 removeMutedWords(removeableMuteWords) 120 } else { 121 upsertMutedWord([ 122 {value: tag, targets: ['tag'], actorTarget: 'all'}, 123 ]) 124 } 125 }, 126 testID: 'tagMenuMute', 127 icon: { 128 ios: { 129 name: 'speaker.slash', 130 }, 131 android: 'ic_menu_sort_alphabetically', 132 web: isMuted ? 'eye' : ['far', 'eye-slash'], 133 }, 134 }, 135 ].filter(Boolean) 136 }, [ 137 _, 138 authorHandle, 139 isMuted, 140 navigation, 141 preferences, 142 tag, 143 truncatedTag, 144 upsertMutedWord, 145 removeMutedWords, 146 removeableMuteWords, 147 ]) 148 149 return ( 150 <EventStopper> 151 <NativeDropdown 152 accessibilityLabel={_(msg`Click here to open tag menu for ${tag}`)} 153 accessibilityHint="" 154 // @ts-ignore 155 items={dropdownItems} 156 triggerStyle={web({ 157 textAlign: 'left', 158 })}> 159 {children} 160 </NativeDropdown> 161 </EventStopper> 162 ) 163}