Bluesky app fork with some witchin' additions 馃挮
1import {type StyleProp, Text as RNText, type TextStyle} from 'react-native'
2import {msg} from '@lingui/core/macro'
3import {useLingui} from '@lingui/react'
4import {Trans} from '@lingui/react/macro'
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 =
71 preferences?.moderationPrefs.mutedWords?.filter(word => {
72 return word.value === tag
73 }) || []
74
75 return (
76 <Menu.Root>
77 <Menu.Trigger label={label} hint={hint}>
78 {({props: menuProps}) => (
79 <InlineLinkText
80 to={{
81 screen: 'Hashtag',
82 params: {tag: encodeURIComponent(tag)},
83 }}
84 {...menuProps}
85 onPress={e => {
86 if (IS_WEB) {
87 return createStaticClickIfUnmodified(() => {
88 if (!IS_NATIVE) {
89 menuProps.onPress()
90 }
91 }).onPress(e)
92 }
93 }}
94 onLongPress={createStaticClick(menuProps.onPress).onPress}
95 accessibilityHint={hint}
96 label={label}
97 style={textStyle}
98 emoji>
99 {IS_NATIVE ? (
100 display
101 ) : (
102 <RNText ref={menuProps.ref}>{display}</RNText>
103 )}
104 </InlineLinkText>
105 )}
106 </Menu.Trigger>
107 <Menu.Outer>
108 <Menu.Group>
109 <Menu.Item
110 label={_(msg`See ${isCashtag ? tag : `#${tag}`} posts`)}
111 onPress={() => {
112 navigation.push('Hashtag', {
113 tag: encodeURIComponent(tag),
114 })
115 }}>
116 <Menu.ItemText>
117 {isCashtag ? (
118 <Trans>See {tag} posts</Trans>
119 ) : (
120 <Trans>See #{tag} posts</Trans>
121 )}
122 </Menu.ItemText>
123 <Menu.ItemIcon icon={Search} />
124 </Menu.Item>
125 {authorHandle && !isInvalidHandle(authorHandle) && (
126 <Menu.Item
127 label={_(msg`See ${isCashtag ? tag : `#${tag}`} posts by user`)}
128 onPress={() => {
129 navigation.push('Hashtag', {
130 tag: encodeURIComponent(tag),
131 author: authorHandle,
132 })
133 }}>
134 <Menu.ItemText>
135 {isCashtag ? (
136 <Trans>See {tag} posts by user</Trans>
137 ) : (
138 <Trans>See #{tag} posts by user</Trans>
139 )}
140 </Menu.ItemText>
141 <Menu.ItemIcon icon={Person} />
142 </Menu.Item>
143 )}
144 </Menu.Group>
145 <Menu.Divider />
146 <Menu.Item
147 label={
148 isMuted
149 ? _(msg`Unmute ${isCashtag ? tag : `#${tag}`}`)
150 : _(msg`Mute ${isCashtag ? tag : `#${tag}`}`)
151 }
152 onPress={() => {
153 if (isMuted) {
154 resetUpsert()
155 removeMutedWords(removeableMuteWords)
156 } else {
157 resetRemove()
158 upsertMutedWord([
159 {value: tag, targets: ['tag'], actorTarget: 'all'},
160 ])
161 }
162 }}>
163 <Menu.ItemText>
164 {isMuted
165 ? _(msg`Unmute ${isCashtag ? tag : `#${tag}`}`)
166 : _(msg`Mute ${isCashtag ? tag : `#${tag}`}`)}
167 </Menu.ItemText>
168 <Menu.ItemIcon icon={isPreferencesLoading ? Loader : Mute} />
169 </Menu.Item>
170 </Menu.Outer>
171 </Menu.Root>
172 )
173}