Activity notification settings (#8485)
Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Samuel Newman <mozzius@protonmail.com> Co-authored-by: hailey <me@haileyok.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyNotificationDeclaration,
|
||||
type AppBskyNotificationListActivitySubscriptions,
|
||||
} from '@atproto/api'
|
||||
import {t} from '@lingui/macro'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
|
||||
export const RQKEY_getActivitySubscriptions = ['activity-subscriptions']
|
||||
export const RQKEY_getNotificationDeclaration = ['notification-declaration']
|
||||
|
||||
export function useActivitySubscriptionsQuery() {
|
||||
const agent = useAgent()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: RQKEY_getActivitySubscriptions,
|
||||
queryFn: async ({pageParam}) => {
|
||||
const response =
|
||||
await agent.app.bsky.notification.listActivitySubscriptions({
|
||||
cursor: pageParam,
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: prev => prev.cursor,
|
||||
})
|
||||
}
|
||||
|
||||
export function useNotificationDeclarationQuery() {
|
||||
const agent = useAgent()
|
||||
const {currentAccount} = useSession()
|
||||
return useQuery({
|
||||
queryKey: RQKEY_getNotificationDeclaration,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const response = await agent.app.bsky.notification.declaration.get({
|
||||
repo: currentAccount!.did,
|
||||
rkey: 'self',
|
||||
})
|
||||
return response
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof Error &&
|
||||
err.message.startsWith('Could not locate record')
|
||||
) {
|
||||
return {
|
||||
value: {
|
||||
$type: 'app.bsky.notification.declaration',
|
||||
allowSubscriptions: 'followers',
|
||||
} satisfies AppBskyNotificationDeclaration.Record,
|
||||
}
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useNotificationDeclarationMutation() {
|
||||
const agent = useAgent()
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async (record: AppBskyNotificationDeclaration.Record) => {
|
||||
const response = await agent.app.bsky.notification.declaration.put(
|
||||
{
|
||||
repo: currentAccount!.did,
|
||||
rkey: 'self',
|
||||
},
|
||||
record,
|
||||
)
|
||||
return response
|
||||
},
|
||||
onMutate: value => {
|
||||
queryClient.setQueryData(
|
||||
RQKEY_getNotificationDeclaration,
|
||||
(old?: {
|
||||
uri: string
|
||||
cid: string
|
||||
value: AppBskyNotificationDeclaration.Record
|
||||
}) => {
|
||||
if (!old) return old
|
||||
return {
|
||||
value,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(t`Failed to update notification declaration`)
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: RQKEY_getNotificationDeclaration,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyNotificationListActivitySubscriptions.OutputSchema>
|
||||
>({
|
||||
queryKey: RQKEY_getActivitySubscriptions,
|
||||
})
|
||||
for (const [_queryKey, queryData] of queryDatas) {
|
||||
if (!queryData?.pages) {
|
||||
continue
|
||||
}
|
||||
for (const page of queryData.pages) {
|
||||
for (const subscription of page.subscriptions) {
|
||||
if (subscription.did === did) {
|
||||
yield subscription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyGraphDefs,
|
||||
AppBskyGraphGetList,
|
||||
BskyAgent,
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphDefs,
|
||||
type AppBskyGraphGetList,
|
||||
type BskyAgent,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
type QueryKey,
|
||||
useInfiniteQuery,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
@@ -100,21 +100,16 @@ export function* findAllProfilesInQueryData(
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
for (const [_queryKey, queryData] of queryDatas) {
|
||||
if (!queryData) {
|
||||
if (!queryData?.pages) {
|
||||
continue
|
||||
}
|
||||
for (const [_queryKey, queryData] of queryDatas) {
|
||||
if (!queryData?.pages) {
|
||||
continue
|
||||
for (const page of queryData?.pages) {
|
||||
if (page.list.creator.did === did) {
|
||||
yield page.list.creator
|
||||
}
|
||||
for (const page of queryData?.pages) {
|
||||
if (page.list.creator.did === did) {
|
||||
yield page.list.creator
|
||||
}
|
||||
for (const item of page.items) {
|
||||
if (item.subject.did === did) {
|
||||
yield item.subject
|
||||
}
|
||||
for (const item of page.items) {
|
||||
if (item.subject.did === did) {
|
||||
yield item.subject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {type AppBskyActorDefs} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
@@ -19,7 +19,7 @@ export function useUpdateActorDeclaration({
|
||||
return useMutation({
|
||||
mutationFn: async (allowIncoming: 'all' | 'none' | 'following') => {
|
||||
if (!currentAccount) throw new Error('Not signed in')
|
||||
const result = await agent.api.com.atproto.repo.putRecord({
|
||||
const result = await agent.com.atproto.repo.putRecord({
|
||||
repo: currentAccount.did,
|
||||
collection: 'chat.bsky.actor.declaration',
|
||||
rkey: 'self',
|
||||
|
||||
@@ -18,30 +18,30 @@
|
||||
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
type AppBskyActorDefs,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
type QueryKey,
|
||||
useInfiniteQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useThreadgateHiddenReplyUris} from '#/state/threadgate-hidden-replies'
|
||||
import {useModerationOpts} from '../../preferences/moderation-opts'
|
||||
import {STALE} from '..'
|
||||
import {
|
||||
didOrHandleUriMatches,
|
||||
embedViewRecordToPostView,
|
||||
getEmbeddedPost,
|
||||
} from '../util'
|
||||
import {FeedPage} from './types'
|
||||
import {type FeedPage} from './types'
|
||||
import {useUnreadNotificationsApi} from './unread'
|
||||
import {fetchPage} from './util'
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ type OtherNotificationType =
|
||||
| 'unverified'
|
||||
| 'like-via-repost'
|
||||
| 'repost-via-repost'
|
||||
| 'subscribed-post'
|
||||
| 'unknown'
|
||||
|
||||
type FeedNotificationBase = {
|
||||
|
||||
@@ -28,6 +28,7 @@ const GROUPABLE_REASONS = [
|
||||
'follow',
|
||||
'like-via-repost',
|
||||
'repost-via-repost',
|
||||
'subscribed-post',
|
||||
]
|
||||
const MS_1HR = 1e3 * 60 * 60
|
||||
const MS_2DAY = MS_1HR * 48
|
||||
@@ -144,7 +145,8 @@ export function groupNotifications(
|
||||
Math.abs(ts2 - ts) < MS_2DAY &&
|
||||
notif.reason === groupedNotif.notification.reason &&
|
||||
notif.reasonSubject === groupedNotif.notification.reasonSubject &&
|
||||
notif.author.did !== groupedNotif.notification.author.did
|
||||
(notif.author.did !== groupedNotif.notification.author.did ||
|
||||
notif.reason === 'subscribed-post')
|
||||
) {
|
||||
const nextIsFollowBack =
|
||||
notif.reason === 'follow' && notif.author.viewer?.following
|
||||
@@ -252,7 +254,8 @@ function toKnownType(
|
||||
notif.reason === 'verified' ||
|
||||
notif.reason === 'unverified' ||
|
||||
notif.reason === 'like-via-repost' ||
|
||||
notif.reason === 'repost-via-repost'
|
||||
notif.reason === 'repost-via-repost' ||
|
||||
notif.reason === 'subscribed-post'
|
||||
) {
|
||||
return notif.reason as NotificationType
|
||||
}
|
||||
@@ -263,7 +266,12 @@ function getSubjectUri(
|
||||
type: NotificationType,
|
||||
notif: AppBskyNotificationListNotifications.Notification,
|
||||
): string | undefined {
|
||||
if (type === 'reply' || type === 'quote' || type === 'mention') {
|
||||
if (
|
||||
type === 'reply' ||
|
||||
type === 'quote' ||
|
||||
type === 'mention' ||
|
||||
type === 'subscribed-post'
|
||||
) {
|
||||
return notif.uri
|
||||
} else if (
|
||||
type === 'post-like' ||
|
||||
|
||||
@@ -6,6 +6,7 @@ export enum Nux {
|
||||
NeueTypography = 'NeueTypography',
|
||||
ExploreInterestsCard = 'ExploreInterestsCard',
|
||||
InitialVerificationAnnouncement = 'InitialVerificationAnnouncement',
|
||||
ActivitySubscriptions = 'ActivitySubscriptions',
|
||||
}
|
||||
|
||||
export const nuxNames = new Set(Object.values(Nux))
|
||||
@@ -23,10 +24,15 @@ export type AppNux = BaseNux<
|
||||
id: Nux.InitialVerificationAnnouncement
|
||||
data: undefined
|
||||
}
|
||||
| {
|
||||
id: Nux.ActivitySubscriptions
|
||||
data: undefined
|
||||
}
|
||||
>
|
||||
|
||||
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
|
||||
[Nux.NeueTypography]: undefined,
|
||||
[Nux.ExploreInterestsCard]: undefined,
|
||||
[Nux.InitialVerificationAnnouncement]: undefined,
|
||||
[Nux.ActivitySubscriptions]: undefined,
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||
import {LikesFeedAPI} from '#/lib/api/feed/likes'
|
||||
import {ListFeedAPI} from '#/lib/api/feed/list'
|
||||
import {MergeFeedAPI} from '#/lib/api/feed/merge'
|
||||
import {PostListFeedAPI} from '#/lib/api/feed/posts'
|
||||
import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip'
|
||||
@@ -53,6 +54,7 @@ export type AuthorFilter =
|
||||
| 'posts_with_video'
|
||||
type FeedUri = string
|
||||
type ListUri = string
|
||||
type PostsUriList = string
|
||||
|
||||
export type FeedDescriptor =
|
||||
| 'following'
|
||||
@@ -60,6 +62,7 @@ export type FeedDescriptor =
|
||||
| `feedgen|${FeedUri}`
|
||||
| `likes|${ActorDid}`
|
||||
| `list|${ListUri}`
|
||||
| `posts|${PostsUriList}`
|
||||
| 'demo'
|
||||
export interface FeedParams {
|
||||
mergeFeedEnabled?: boolean
|
||||
@@ -488,6 +491,9 @@ function createApi({
|
||||
} else if (feedDesc.startsWith('list')) {
|
||||
const [_, list] = feedDesc.split('|')
|
||||
return new ListFeedAPI({agent, feedParams: {list}})
|
||||
} else if (feedDesc.startsWith('posts')) {
|
||||
const [_, uriList] = feedDesc.split('|')
|
||||
return new PostListFeedAPI({agent, feedParams: {uris: uriList.split(',')}})
|
||||
} else if (feedDesc === 'demo') {
|
||||
return new DemoFeedAPI({agent})
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user