about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/components/TagMenu/index.tsx6
-rw-r--r--src/components/TagMenu/index.web.tsx5
-rw-r--r--src/components/dialogs/MutedWords.tsx60
-rw-r--r--yarn.lock40
5 files changed, 89 insertions, 24 deletions
diff --git a/package.json b/package.json
index e9dd9202d..378d520bc 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
     "update-extensions": "scripts/updateExtensions.sh"
   },
   "dependencies": {
-    "@atproto/api": "^0.10.0",
+    "@atproto/api": "^0.10.3",
     "@bam.tech/react-native-image-resizer": "^3.0.4",
     "@braintree/sanitize-url": "^6.0.2",
     "@emoji-mart/react": "^1.1.1",
diff --git a/src/components/TagMenu/index.tsx b/src/components/TagMenu/index.tsx
index 2fec7a188..c18c0d6a2 100644
--- a/src/components/TagMenu/index.tsx
+++ b/src/components/TagMenu/index.tsx
@@ -215,14 +215,12 @@ export function TagMenu({
                           if (isMuted) {
                             resetUpsert()
                             removeMutedWord({
-                              value: sanitizedTag,
+                              value: tag,
                               targets: ['tag'],
                             })
                           } else {
                             resetRemove()
-                            upsertMutedWord([
-                              {value: sanitizedTag, targets: ['tag']},
-                            ])
+                            upsertMutedWord([{value: tag, targets: ['tag']}])
                           }
                         })
                       }}>
diff --git a/src/components/TagMenu/index.web.tsx b/src/components/TagMenu/index.web.tsx
index 3aebfbba2..4fcb4c812 100644
--- a/src/components/TagMenu/index.web.tsx
+++ b/src/components/TagMenu/index.web.tsx
@@ -104,9 +104,9 @@ export function TagMenu({
           : _(msg`Mute ${truncatedTag}`),
         onPress() {
           if (isMuted) {
-            removeMutedWord({value: sanitizedTag, targets: ['tag']})
+            removeMutedWord({value: tag, targets: ['tag']})
           } else {
-            upsertMutedWord([{value: sanitizedTag, targets: ['tag']}])
+            upsertMutedWord([{value: tag, targets: ['tag']}])
           }
         },
         testID: 'tagMenuMute',
@@ -127,7 +127,6 @@ export function TagMenu({
     preferences,
     tag,
     truncatedTag,
-    sanitizedTag,
     upsertMutedWord,
     removeMutedWord,
   ])
diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx
index 7c0d4fbca..453b13513 100644
--- a/src/components/dialogs/MutedWords.tsx
+++ b/src/components/dialogs/MutedWords.tsx
@@ -2,7 +2,7 @@ import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api'
 
 import {
   usePreferencesQuery,
@@ -10,7 +10,14 @@ import {
   useRemoveMutedWordMutation,
 } from '#/state/queries/preferences'
 import {isNative} from '#/platform/detection'
-import {atoms as a, useTheme, useBreakpoints, ViewStyleProp, web} from '#/alf'
+import {
+  atoms as a,
+  useTheme,
+  useBreakpoints,
+  ViewStyleProp,
+  web,
+  native,
+} from '#/alf'
 import {Text} from '#/components/Typography'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
@@ -48,24 +55,29 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
   const {isPending, mutateAsync: addMutedWord} = useUpsertMutedWordsMutation()
   const [field, setField] = React.useState('')
   const [options, setOptions] = React.useState(['content'])
-  const [_error, setError] = React.useState('')
+  const [error, setError] = React.useState('')
 
   const submit = React.useCallback(async () => {
-    const value = field.trim()
+    const sanitizedValue = sanitizeMutedWordValue(field)
     const targets = ['tag', options.includes('content') && 'content'].filter(
       Boolean,
     ) as AppBskyActorDefs.MutedWord['targets']
 
-    if (!value || !targets.length) return
+    if (!sanitizedValue || !targets.length) {
+      setField('')
+      setError(_(msg`Please enter a valid word, tag, or phrase to mute`))
+      return
+    }
 
     try {
-      await addMutedWord([{value, targets}])
+      // send raw value and rely on SDK as sanitization source of truth
+      await addMutedWord([{value: field, targets}])
       setField('')
     } catch (e: any) {
       logger.error(`Failed to save muted word`, {message: e.message})
       setError(e.message)
     }
-  }, [field, options, addMutedWord, setField])
+  }, [_, field, options, addMutedWord, setField])
 
   return (
     <Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}>
@@ -87,7 +99,12 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
           label={_(msg`Enter a word or tag`)}
           placeholder={_(msg`Enter a word or tag`)}
           value={field}
-          onChangeText={setField}
+          onChangeText={value => {
+            if (error) {
+              setError('')
+            }
+            setField(value)
+          }}
           onSubmitEditing={submit}
         />
 
@@ -99,7 +116,7 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
           <View
             style={[
               a.pt_sm,
-              a.pb_md,
+              a.py_sm,
               a.flex_row,
               a.align_center,
               a.gap_sm,
@@ -151,8 +168,33 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
           </View>
         </Toggle.Group>
 
+        {error && (
+          <View
+            style={[
+              a.mb_lg,
+              a.flex_row,
+              a.rounded_sm,
+              a.p_md,
+              a.mb_xs,
+              t.atoms.bg_contrast_25,
+              {
+                backgroundColor: t.palette.negative_400,
+              },
+            ]}>
+            <Text
+              style={[
+                a.italic,
+                {color: t.palette.white},
+                native({marginTop: 2}),
+              ]}>
+              {error}
+            </Text>
+          </View>
+        )}
+
         <Text
           style={[
+            a.pt_xs,
             a.text_sm,
             a.italic,
             a.leading_snug,
diff --git a/yarn.lock b/yarn.lock
index a62ff2f83..d34166925 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -34,15 +34,15 @@
     jsonpointer "^5.0.0"
     leven "^3.1.0"
 
-"@atproto/api@^0.10.0":
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.10.0.tgz#ca34dfa8f9b1e6ba021094c40cb0ff3c4c254044"
-  integrity sha512-TSVCHh3UUZLtNzh141JwLicfYTc7TvVFvQJSWeOZLHr3Sk+9hqEY+9Itaqp1DAW92r4i25ChaMc/50sg4etAWQ==
+"@atproto/api@^0.10.3":
+  version "0.10.3"
+  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.10.3.tgz#cdef37a23b53e2e84840e527992482b0a0ab9d47"
+  integrity sha512-eNP/6YLor48SUD38Jn5C7xocQL9XzW+BbYat2whWerKsFMn6Kfkk5O3fW1pvcc6NKtKVTo7/4ixMAS+dG2o/Yg==
   dependencies:
     "@atproto/common-web" "^0.2.3"
-    "@atproto/lexicon" "^0.3.1"
-    "@atproto/syntax" "^0.1.5"
-    "@atproto/xrpc" "^0.4.1"
+    "@atproto/lexicon" "^0.3.2"
+    "@atproto/syntax" "^0.2.0"
+    "@atproto/xrpc" "^0.4.2"
     multiformats "^9.9.0"
     tlds "^1.234.0"
     typed-emitter "^2.1.0"
@@ -245,6 +245,17 @@
     multiformats "^9.9.0"
     zod "^3.21.4"
 
+"@atproto/lexicon@^0.3.2":
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.3.2.tgz#0085a3acd3a77867b8efe188297a1bbacc55ce5c"
+  integrity sha512-kmGCkrRwpWIqmn/KO4BZwUf8Nmfndk3XvFC06V0ygCWc42g6+t4QP/6ywNW4PgqfZY0Q5aW4EuDfD7KjAFkFtQ==
+  dependencies:
+    "@atproto/common-web" "^0.2.3"
+    "@atproto/syntax" "^0.2.0"
+    iso-datestring-validator "^2.2.2"
+    multiformats "^9.9.0"
+    zod "^3.21.4"
+
 "@atproto/ozone@^0.0.7":
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.0.7.tgz#bfad82bc1d0900e79401a82f13581f707415505a"
@@ -340,6 +351,13 @@
   dependencies:
     "@atproto/common-web" "^0.2.3"
 
+"@atproto/syntax@^0.2.0":
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.2.0.tgz#4bab724c02e11f8943b8ec101251082cf55067e9"
+  integrity sha512-K+9jl6mtxC9ytlR7msSiP9jVNqtdxEBSt0kOfsC924lqGwuD8nlUAMi1GSMgAZJGg/Rd+0MKXh789heTdeL3HQ==
+  dependencies:
+    "@atproto/common-web" "^0.2.3"
+
 "@atproto/xrpc-server@^0.4.2":
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.4.2.tgz#23efd89086b85933f1b0cc00c86e895adcaac315"
@@ -365,6 +383,14 @@
     "@atproto/lexicon" "^0.3.1"
     zod "^3.21.4"
 
+"@atproto/xrpc@^0.4.2":
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.4.2.tgz#57812e0624be597b85f21471acf336513f35ccda"
+  integrity sha512-x4x2QB4nWmLjIpz2Ue9n/QVbVyJkk6tQMhvmDQaVFF89E3FcVI4rxF4uhzSxaLpbNtyVQBNEEmNHOr5EJLeHVA==
+  dependencies:
+    "@atproto/lexicon" "^0.3.2"
+    zod "^3.21.4"
+
 "@aws-crypto/crc32@3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa"