about summary refs log tree commit diff
path: root/src/components/RichTextTag.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/RichTextTag.tsx')
-rw-r--r--src/components/RichTextTag.tsx160
1 files changed, 160 insertions, 0 deletions
diff --git a/src/components/RichTextTag.tsx b/src/components/RichTextTag.tsx
new file mode 100644
index 000000000..562d44aa6
--- /dev/null
+++ b/src/components/RichTextTag.tsx
@@ -0,0 +1,160 @@
+import React from 'react'
+import {StyleProp, Text as RNText, TextStyle} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {NavigationProp} from '#/lib/routes/types'
+import {isInvalidHandle} from '#/lib/strings/handles'
+import {isNative, isWeb} from '#/platform/detection'
+import {
+  usePreferencesQuery,
+  useRemoveMutedWordsMutation,
+  useUpsertMutedWordsMutation,
+} from '#/state/queries/preferences'
+import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
+import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
+import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
+import {
+  createStaticClick,
+  createStaticClickIfUnmodified,
+  InlineLinkText,
+} from '#/components/Link'
+import {Loader} from '#/components/Loader'
+import * as Menu from '#/components/Menu'
+
+export function RichTextTag({
+  tag,
+  display,
+  authorHandle,
+  textStyle,
+}: {
+  tag: string
+  display: string
+  authorHandle?: string
+  textStyle: StyleProp<TextStyle>
+}) {
+  const {_} = useLingui()
+  const {isLoading: isPreferencesLoading, data: preferences} =
+    usePreferencesQuery()
+  const {
+    mutateAsync: upsertMutedWord,
+    variables: optimisticUpsert,
+    reset: resetUpsert,
+  } = useUpsertMutedWordsMutation()
+  const {
+    mutateAsync: removeMutedWords,
+    variables: optimisticRemove,
+    reset: resetRemove,
+  } = useRemoveMutedWordsMutation()
+  const navigation = useNavigation<NavigationProp>()
+  const label = _(msg`Hashtag ${tag}`)
+  const hint = isNative
+    ? _(msg`Long press to open tag menu for #${tag}`)
+    : _(msg`Click to open tag menu for ${tag}`)
+
+  const isMuted = Boolean(
+    (preferences?.moderationPrefs.mutedWords?.find(
+      m => m.value === tag && m.targets.includes('tag'),
+    ) ??
+      optimisticUpsert?.find(
+        m => m.value === tag && m.targets.includes('tag'),
+      )) &&
+      !optimisticRemove?.find(m => m?.value === tag),
+  )
+
+  /*
+   * Mute word records that exactly match the tag in question.
+   */
+  const removeableMuteWords = React.useMemo(() => {
+    return (
+      preferences?.moderationPrefs.mutedWords?.filter(word => {
+        return word.value === tag
+      }) || []
+    )
+  }, [tag, preferences?.moderationPrefs?.mutedWords])
+
+  return (
+    <Menu.Root>
+      <Menu.Trigger label={label} hint={hint}>
+        {({props: menuProps}) => (
+          <InlineLinkText
+            to={{
+              screen: 'Hashtag',
+              params: {tag: encodeURIComponent(tag)},
+            }}
+            {...menuProps}
+            onPress={e => {
+              if (isWeb) {
+                return createStaticClickIfUnmodified(() => {
+                  if (!isNative) {
+                    menuProps.onPress()
+                  }
+                }).onPress(e)
+              }
+            }}
+            onLongPress={createStaticClick(menuProps.onPress).onPress}
+            accessibilityHint={hint}
+            label={label}
+            style={textStyle}>
+            {isNative ? (
+              display
+            ) : (
+              <RNText ref={menuProps.ref}>{display}</RNText>
+            )}
+          </InlineLinkText>
+        )}
+      </Menu.Trigger>
+      <Menu.Outer>
+        <Menu.Group>
+          <Menu.Item
+            label={_(msg`See ${tag} posts`)}
+            onPress={() => {
+              navigation.push('Hashtag', {
+                tag: encodeURIComponent(tag),
+              })
+            }}>
+            <Menu.ItemText>
+              <Trans>See #{tag} posts</Trans>
+            </Menu.ItemText>
+            <Menu.ItemIcon icon={Search} />
+          </Menu.Item>
+          {authorHandle && !isInvalidHandle(authorHandle) && (
+            <Menu.Item
+              label={_(msg`See ${tag} posts by user`)}
+              onPress={() => {
+                navigation.push('Hashtag', {
+                  tag: encodeURIComponent(tag),
+                  author: authorHandle,
+                })
+              }}>
+              <Menu.ItemText>
+                <Trans>See #{tag} posts by user</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon icon={Person} />
+            </Menu.Item>
+          )}
+        </Menu.Group>
+        <Menu.Divider />
+        <Menu.Item
+          label={isMuted ? _(msg`Unmute ${tag}`) : _(msg`Mute ${tag}`)}
+          onPress={() => {
+            if (isMuted) {
+              resetUpsert()
+              removeMutedWords(removeableMuteWords)
+            } else {
+              resetRemove()
+              upsertMutedWord([
+                {value: tag, targets: ['tag'], actorTarget: 'all'},
+              ])
+            }
+          }}>
+          <Menu.ItemText>
+            {isMuted ? _(msg`Unmute ${tag}`) : _(msg`Mute ${tag}`)}
+          </Menu.ItemText>
+          <Menu.ItemIcon icon={isPreferencesLoading ? Loader : Mute} />
+        </Menu.Item>
+      </Menu.Outer>
+    </Menu.Root>
+  )
+}