about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-02-11 16:07:10 -0600
committerGitHub <noreply@github.com>2025-02-11 14:07:10 -0800
commit459611ca7a0d1559e3c9373ef9a61ea8e4c1e2f7 (patch)
tree12808a7ee8f9abce9bf06c50b047a9c426fc04f1
parent03f75e8ff49d8fa0f7b96321ba8d3a5dd8841703 (diff)
downloadvoidsky-459611ca7a0d1559e3c9373ef9a61ea8e4c1e2f7.tar.zst
[APP-1031] Add new followerRule to threadgate settings (#7681)
* Add new followerRule to threadgate settings

* Handle WhoCanReply copy

* Handle follow case

* fix ci

* Revert "Handle follow case"

This reverts commit bc454dad896fe577bec91f3d65d971ad9e0fec8d.

* Hide Follow button if followedBy rule enabled

* Revert "Revert "Handle follow case""

This reverts commit cadc46d2dc50120424ed460943775c58efc59c4d.

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
-rw-r--r--package.json2
-rw-r--r--src/components/WhoCanReply.tsx13
-rw-r--r--src/components/dialogs/PostInteractionSettingsDialog.tsx12
-rw-r--r--src/state/queries/threadgate/types.ts1
-rw-r--r--src/state/queries/threadgate/util.ts4
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx7
-rw-r--r--src/view/com/util/post-ctrls/PostCtrls.tsx14
-rw-r--r--yarn.lock54
8 files changed, 94 insertions, 13 deletions
diff --git a/package.json b/package.json
index bc161b8bc..708ed7ccf 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
     "icons:optimize": "svgo -f ./assets/icons"
   },
   "dependencies": {
-    "@atproto/api": "^0.13.33",
+    "@atproto/api": "^0.13.35",
     "@bitdrift/react-native": "^0.6.2",
     "@braintree/sanitize-url": "^6.0.2",
     "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
diff --git a/src/components/WhoCanReply.tsx b/src/components/WhoCanReply.tsx
index 34ac3fe5b..7d74a50c6 100644
--- a/src/components/WhoCanReply.tsx
+++ b/src/components/WhoCanReply.tsx
@@ -258,6 +258,19 @@ function Rule({
   if (rule.type === 'mention') {
     return <Trans>mentioned users</Trans>
   }
+  if (rule.type === 'followers') {
+    return (
+      <Trans>
+        users following{' '}
+        <InlineLinkText
+          label={`@${post.author.handle}`}
+          to={makeProfileLink(post.author)}
+          style={[a.text_sm, a.leading_snug]}>
+          @{post.author.handle}
+        </InlineLinkText>
+      </Trans>
+    )
+  }
   if (rule.type === 'following') {
     return (
       <Trans>
diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx
index b443d59f2..e4bc77702 100644
--- a/src/components/dialogs/PostInteractionSettingsDialog.tsx
+++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx
@@ -429,7 +429,7 @@ export function PostInteractionSettingsForm({
                     disabled={replySettingsDisabled}
                   />
                   <Selectable
-                    label={_(msg`Followed users`)}
+                    label={_(msg`Users you follow`)}
                     isSelected={
                       !!threadgateAllowUISettings.find(
                         v => v.type === 'following',
@@ -438,6 +438,16 @@ export function PostInteractionSettingsForm({
                     onPress={() => onPressAudience({type: 'following'})}
                     disabled={replySettingsDisabled}
                   />
+                  <Selectable
+                    label={_(msg`Your followers`)}
+                    isSelected={
+                      !!threadgateAllowUISettings.find(
+                        v => v.type === 'followers',
+                      )
+                    }
+                    onPress={() => onPressAudience({type: 'followers'})}
+                    disabled={replySettingsDisabled}
+                  />
                   {lists && lists.length > 0
                     ? lists.map(list => (
                         <Selectable
diff --git a/src/state/queries/threadgate/types.ts b/src/state/queries/threadgate/types.ts
index 0cbea311c..56eadabcd 100644
--- a/src/state/queries/threadgate/types.ts
+++ b/src/state/queries/threadgate/types.ts
@@ -3,4 +3,5 @@ export type ThreadgateAllowUISetting =
   | {type: 'nobody'}
   | {type: 'mention'}
   | {type: 'following'}
+  | {type: 'followers'}
   | {type: 'list'; list: unknown}
diff --git a/src/state/queries/threadgate/util.ts b/src/state/queries/threadgate/util.ts
index 09ae0a0c1..4459eddbe 100644
--- a/src/state/queries/threadgate/util.ts
+++ b/src/state/queries/threadgate/util.ts
@@ -43,6 +43,8 @@ export function threadgateRecordToAllowUISetting(
         setting = {type: 'mention'}
       } else if (allow.$type === 'app.bsky.feed.threadgate#followingRule') {
         setting = {type: 'following'}
+      } else if (allow.$type === 'app.bsky.feed.threadgate#followerRule') {
+        setting = {type: 'followers'}
       } else if (allow.$type === 'app.bsky.feed.threadgate#listRule') {
         setting = {type: 'list', list: allow.list}
       }
@@ -79,6 +81,8 @@ export function threadgateAllowUISettingToAllowRecordValue(
         allow.push({$type: 'app.bsky.feed.threadgate#mentionRule'})
       } else if (rule.type === 'following') {
         allow.push({$type: 'app.bsky.feed.threadgate#followingRule'})
+      } else if (rule.type === 'followers') {
+        allow.push({$type: 'app.bsky.feed.threadgate#followerRule'})
       } else if (rule.type === 'list') {
         allow.push({
           $type: 'app.bsky.feed.threadgate#listRule',
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 4f950e261..928ccd783 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -241,6 +241,11 @@ let PostThreadItemLoaded = ({
     return makeProfileLink(post.author, 'post', urip.rkey, 'quotes')
   }, [post.uri, post.author])
   const quotesTitle = _(msg`Quotes of this post`)
+  const onlyFollowersCanReply = !!threadgateRecord?.allow?.find(
+    rule => rule.$type === 'app.bsky.feed.threadgate#followerRule',
+  )
+  const showFollowButton =
+    currentAccount?.did !== post.author.did && !onlyFollowersCanReply
 
   const translatorUrl = getTranslatorLink(
     record?.text || '',
@@ -343,7 +348,7 @@ let PostThreadItemLoaded = ({
                 </Text>
               </Link>
             </View>
-            {currentAccount?.did !== post.author.did && (
+            {showFollowButton && (
               <View>
                 <PostThreadFollowBtn did={post.author.did} />
               </View>
diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx
index f73cede35..b61bad2ca 100644
--- a/src/view/com/util/post-ctrls/PostCtrls.tsx
+++ b/src/view/com/util/post-ctrls/PostCtrls.tsx
@@ -26,6 +26,7 @@ import {makeProfileLink} from '#/lib/routes/links'
 import {shareUrl} from '#/lib/sharing'
 import {useGate} from '#/lib/statsig/statsig'
 import {toShareUrl} from '#/lib/strings/url-helpers'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {Shadow} from '#/state/cache/types'
 import {useFeedFeedbackContext} from '#/state/feed-feedback'
 import {
@@ -94,6 +95,15 @@ let PostCtrls = ({
       post.author.viewer?.blockingByList,
   )
 
+  const shadowedAuthor = useProfileShadow(post.author)
+  const followersCanReply = !!threadgateRecord?.allow?.find(
+    rule => rule.$type === 'app.bsky.feed.threadgate#followerRule',
+  )
+  const canOverrideReplyDisabled =
+    followersCanReply &&
+    shadowedAuthor.viewer?.following?.startsWith('at://did')
+  const replyDisabled = post.viewer?.replyDisabled && !canOverrideReplyDisabled
+
   const shouldShowLoggedOutWarning = React.useMemo(() => {
     return (
       post.author.did !== currentAccount?.did &&
@@ -247,13 +257,13 @@ let PostCtrls = ({
       <View
         style={[
           big ? a.align_center : [a.flex_1, a.align_start, {marginLeft: -6}],
-          post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
+          replyDisabled ? {opacity: 0.5} : undefined,
         ]}>
         <Pressable
           testID="replyBtn"
           style={btnStyle}
           onPress={() => {
-            if (!post.viewer?.replyDisabled) {
+            if (!replyDisabled) {
               playHaptic('Light')
               requireAuth(() => onPressReply())
             }
diff --git a/yarn.lock b/yarn.lock
index f6a3781c0..e2792a4cf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -72,10 +72,10 @@
     tlds "^1.234.0"
     zod "^3.23.8"
 
-"@atproto/api@^0.13.33":
-  version "0.13.33"
-  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.33.tgz#01d31a1cfd1be311e11324b810b8a83fe4cbf9b0"
-  integrity sha512-d8AOvtxo2J2zrmcakJTUtLdz2ns+pAqywNXhPxPzHrHcw79D6MKBLHR0vr8oxkGwhDBQTsHiQWTk4gSo8PF7YA==
+"@atproto/api@^0.13.35":
+  version "0.13.35"
+  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.35.tgz#1e3a6c6e035c8e06302508983ed206effc92a7e8"
+  integrity sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g==
   dependencies:
     "@atproto/common-web" "^0.4.0"
     "@atproto/lexicon" "^0.4.6"
@@ -3295,7 +3295,7 @@
     "@babel/parser" "^7.25.9"
     "@babel/types" "^7.25.9"
 
-"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9":
+"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3":
   version "7.25.9"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84"
   integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==
@@ -3356,6 +3356,19 @@
     debug "^4.3.1"
     globals "^11.1.0"
 
+"@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9":
+  version "7.25.9"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84"
+  integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==
+  dependencies:
+    "@babel/code-frame" "^7.25.9"
+    "@babel/generator" "^7.25.9"
+    "@babel/parser" "^7.25.9"
+    "@babel/template" "^7.25.9"
+    "@babel/types" "^7.25.9"
+    debug "^4.3.1"
+    globals "^11.1.0"
+
 "@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
   version "7.22.10"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03"
@@ -17644,7 +17657,16 @@ string-natural-compare@^3.0.1:
   resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
   integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
 
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -17744,7 +17766,7 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -17758,6 +17780,13 @@ strip-ansi@^5.2.0:
   dependencies:
     ansi-regex "^4.1.0"
 
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
 strip-ansi@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -19032,7 +19061,7 @@ wordwrap@^1.0.0:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
   integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -19050,6 +19079,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"