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