forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}