diff options
Diffstat (limited to 'src/state/queries')
-rw-r--r-- | src/state/queries/email-verification-required.ts | 25 | ||||
-rw-r--r-- | src/state/queries/messages/conversation.ts | 39 | ||||
-rw-r--r-- | src/state/queries/messages/list-converations.tsx | 16 | ||||
-rw-r--r-- | src/state/queries/post-feed.ts | 2 | ||||
-rw-r--r-- | src/state/queries/post-thread.ts | 32 | ||||
-rw-r--r-- | src/state/queries/post.ts | 24 | ||||
-rw-r--r-- | src/state/queries/preferences/const.ts | 2 | ||||
-rw-r--r-- | src/state/queries/preferences/types.ts | 2 | ||||
-rw-r--r-- | src/state/queries/profile.ts | 14 |
9 files changed, 123 insertions, 33 deletions
diff --git a/src/state/queries/email-verification-required.ts b/src/state/queries/email-verification-required.ts new file mode 100644 index 000000000..94ff5cbc6 --- /dev/null +++ b/src/state/queries/email-verification-required.ts @@ -0,0 +1,25 @@ +import {useQuery} from '@tanstack/react-query' + +interface ServiceConfig { + checkEmailConfirmed: boolean +} + +export function useServiceConfigQuery() { + return useQuery({ + queryKey: ['service-config'], + queryFn: async () => { + const res = await fetch( + 'https://api.bsky.app/xrpc/app.bsky.unspecced.getConfig', + ) + if (!res.ok) { + return { + checkEmailConfirmed: false, + } + } + + const json = await res.json() + return json as ServiceConfig + }, + staleTime: 5 * 60 * 1000, + }) +} diff --git a/src/state/queries/messages/conversation.ts b/src/state/queries/messages/conversation.ts index fa8a883d0..db96d21a9 100644 --- a/src/state/queries/messages/conversation.ts +++ b/src/state/queries/messages/conversation.ts @@ -5,7 +5,11 @@ import {STALE} from '#/state/queries' import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useOnMarkAsRead} from '#/state/queries/messages/list-converations' import {useAgent} from '#/state/session' -import {RQKEY as LIST_CONVOS_KEY} from './list-converations' +import { + ConvoListQueryData, + getConvoFromQueryData, + RQKEY as LIST_CONVOS_KEY, +} from './list-converations' const RQKEY_ROOT = 'convo' export const RQKEY = (convoId: string) => [RQKEY_ROOT, convoId] @@ -57,8 +61,37 @@ export function useMarkAsReadMutation() { if (!convoId) throw new Error('No convoId provided') optimisticUpdate(convoId) }, - onSettled() { - queryClient.invalidateQueries({queryKey: LIST_CONVOS_KEY}) + onSuccess(_, {convoId}) { + if (!convoId) return + + queryClient.setQueryData(LIST_CONVOS_KEY, (old: ConvoListQueryData) => { + if (!old) return old + + const existingConvo = getConvoFromQueryData(convoId, old) + + if (existingConvo) { + return { + ...old, + pages: old.pages.map(page => { + return { + ...page, + convos: page.convos.map(convo => { + if (convo.id === convoId) { + return { + ...convo, + unreadCount: 0, + } + } + return convo + }), + } + }), + } + } else { + // If we somehow marked a convo as read that doesn't exist in the + // list, then we don't need to do anything. + } + }) }, }) } diff --git a/src/state/queries/messages/list-converations.tsx b/src/state/queries/messages/list-converations.tsx index eeab246ab..ae379f962 100644 --- a/src/state/queries/messages/list-converations.tsx +++ b/src/state/queries/messages/list-converations.tsx @@ -39,7 +39,7 @@ export function useListConvosQuery({ queryKey: RQKEY, queryFn: async ({pageParam}) => { const {data} = await agent.api.chat.bsky.convo.listConvos( - {cursor: pageParam}, + {cursor: pageParam, limit: 20}, {headers: DM_SERVICE_HEADERS}, ) @@ -47,9 +47,6 @@ export function useListConvosQuery({ }, initialPageParam: undefined as RQPageParam, getNextPageParam: lastPage => lastPage.cursor, - // refetch every 60 seconds since we can't get *all* info from the logs - // i.e. reading chats on another device won't update the unread count - refetchInterval: 60_000, }) } @@ -180,6 +177,11 @@ export function ListConvosProviderInner({ }), } } else { + /** + * We received a message from an conversation old enough that + * it doesn't exist in the query cache, meaning we need to + * refetch and bump the old convo to the top. + */ debouncedRefetch() } }) @@ -245,12 +247,12 @@ export function useUnreadMessageCount() { return useMemo(() => { return { count, - numUnread: count > 0 ? (count > 30 ? '30+' : String(count)) : undefined, + numUnread: count > 0 ? (count > 10 ? '10+' : String(count)) : undefined, } }, [count]) } -type ConvoListQueryData = { +export type ConvoListQueryData = { pageParams: Array<string | undefined> pages: Array<ChatBskyConvoListConvos.OutputSchema> } @@ -301,7 +303,7 @@ function optimisticDelete(chatId: string, old: ConvoListQueryData) { } } -function getConvoFromQueryData(chatId: string, old: ConvoListQueryData) { +export function getConvoFromQueryData(chatId: string, old: ConvoListQueryData) { for (const page of old.pages) { for (const convo of page.convos) { if (convo.id === chatId) { diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index b2e9dcd4c..016d8893b 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -602,7 +602,7 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) { } if (!somePostsPassModeration) { - throw new Error(KnownError.FeedNSFPublic) + throw new Error(KnownError.FeedSignedInOnly) } } diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts index 103a1d03b..4784a9d75 100644 --- a/src/state/queries/post-thread.ts +++ b/src/state/queries/post-thread.ts @@ -216,6 +216,17 @@ export function sortThread( } } + const aPin = Boolean(a.record.text.trim() === '📌') + const bPin = Boolean(b.record.text.trim() === '📌') + if (aPin !== bPin) { + if (aPin) { + return 1 + } + if (bPin) { + return -1 + } + } + if (opts.prioritizeFollowedUsers) { const af = a.post.author.viewer?.following const bf = b.post.author.viewer?.following @@ -226,7 +237,11 @@ export function sortThread( } } - if (opts.sort === 'oldest') { + if (opts.sort === 'hotness') { + const aHotness = getHotness(a.post) + const bHotness = getHotness(b.post) + return bHotness - aHotness + } else if (opts.sort === 'oldest') { return a.post.indexedAt.localeCompare(b.post.indexedAt) } else if (opts.sort === 'newest') { return b.post.indexedAt.localeCompare(a.post.indexedAt) @@ -258,6 +273,21 @@ export function sortThread( // internal methods // = +// Inspired by https://join-lemmy.org/docs/contributors/07-ranking-algo.html +// We want to give recent comments a real chance (and not bury them deep below the fold) +// while also surfacing well-liked comments from the past. In the future, we can explore +// something more sophisticated, but we don't have much data on the client right now. +function getHotness(post: AppBskyFeedDefs.PostView) { + const hoursAgo = + (new Date().getTime() - new Date(post.indexedAt).getTime()) / + (1000 * 60 * 60) + const likeCount = post.likeCount ?? 0 + const likeOrder = Math.log(3 + likeCount) + const timePenaltyExponent = 1.5 + 1.5 / (1 + Math.log(1 + likeCount)) + const timePenalty = Math.pow(hoursAgo + 2, timePenaltyExponent) + return likeOrder / timePenalty +} + function responseToThreadNodes( node: ThreadViewNode, depth = 0, diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index 7023580bb..7052590ca 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -98,8 +98,8 @@ export function useGetPosts() { export function usePostLikeMutationQueue( post: Shadow<AppBskyFeedDefs.PostView>, - logContext: LogEvents['post:like:sampled']['logContext'] & - LogEvents['post:unlike:sampled']['logContext'], + logContext: LogEvents['post:like']['logContext'] & + LogEvents['post:unlike']['logContext'], ) { const queryClient = useQueryClient() const postUri = post.uri @@ -157,7 +157,7 @@ export function usePostLikeMutationQueue( } function usePostLikeMutation( - logContext: LogEvents['post:like:sampled']['logContext'], + logContext: LogEvents['post:like']['logContext'], post: Shadow<AppBskyFeedDefs.PostView>, ) { const {currentAccount} = useSession() @@ -174,7 +174,7 @@ function usePostLikeMutation( if (currentAccount) { ownProfile = findProfileQueryData(queryClient, currentAccount.did) } - logEvent('post:like:sampled', { + logEvent('post:like', { logContext, doesPosterFollowLiker: postAuthor.viewer ? Boolean(postAuthor.viewer.followedBy) @@ -196,12 +196,12 @@ function usePostLikeMutation( } function usePostUnlikeMutation( - logContext: LogEvents['post:unlike:sampled']['logContext'], + logContext: LogEvents['post:unlike']['logContext'], ) { const agent = useAgent() return useMutation<void, Error, {postUri: string; likeUri: string}>({ mutationFn: ({likeUri}) => { - logEvent('post:unlike:sampled', {logContext}) + logEvent('post:unlike', {logContext}) return agent.deleteLike(likeUri) }, }) @@ -209,8 +209,8 @@ function usePostUnlikeMutation( export function usePostRepostMutationQueue( post: Shadow<AppBskyFeedDefs.PostView>, - logContext: LogEvents['post:repost:sampled']['logContext'] & - LogEvents['post:unrepost:sampled']['logContext'], + logContext: LogEvents['post:repost']['logContext'] & + LogEvents['post:unrepost']['logContext'], ) { const queryClient = useQueryClient() const postUri = post.uri @@ -266,7 +266,7 @@ export function usePostRepostMutationQueue( } function usePostRepostMutation( - logContext: LogEvents['post:repost:sampled']['logContext'], + logContext: LogEvents['post:repost']['logContext'], ) { const agent = useAgent() return useMutation< @@ -275,19 +275,19 @@ function usePostRepostMutation( {uri: string; cid: string} // the post's uri and cid >({ mutationFn: post => { - logEvent('post:repost:sampled', {logContext}) + logEvent('post:repost', {logContext}) return agent.repost(post.uri, post.cid) }, }) } function usePostUnrepostMutation( - logContext: LogEvents['post:unrepost:sampled']['logContext'], + logContext: LogEvents['post:unrepost']['logContext'], ) { const agent = useAgent() return useMutation<void, Error, {postUri: string; repostUri: string}>({ mutationFn: ({repostUri}) => { - logEvent('post:unrepost:sampled', {logContext}) + logEvent('post:unrepost', {logContext}) return agent.deleteRepost(repostUri) }, }) diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index e07f40ec5..549f7ce29 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -15,7 +15,7 @@ export const DEFAULT_HOME_FEED_PREFS: UsePreferencesQueryResponse['feedViewPrefs } export const DEFAULT_THREAD_VIEW_PREFS: ThreadViewPreferences = { - sort: 'newest', + sort: 'hotness', prioritizeFollowedUsers: true, lab_treeViewEnabled: false, } diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts index 928bb90da..8f523fcf2 100644 --- a/src/state/queries/preferences/types.ts +++ b/src/state/queries/preferences/types.ts @@ -22,6 +22,6 @@ export type ThreadViewPreferences = Pick< BskyThreadViewPreference, 'prioritizeFollowedUsers' > & { - sort: 'oldest' | 'newest' | 'most-likes' | 'random' | string + sort: 'hotness' | 'oldest' | 'newest' | 'most-likes' | 'random' | string lab_treeViewEnabled?: boolean } diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 3059d9efe..63c405788 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -221,8 +221,8 @@ export function useProfileUpdateMutation() { export function useProfileFollowMutationQueue( profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, - logContext: LogEvents['profile:follow:sampled']['logContext'] & - LogEvents['profile:follow:sampled']['logContext'], + logContext: LogEvents['profile:follow']['logContext'] & + LogEvents['profile:follow']['logContext'], ) { const agent = useAgent() const queryClient = useQueryClient() @@ -293,7 +293,7 @@ export function useProfileFollowMutationQueue( } function useProfileFollowMutation( - logContext: LogEvents['profile:follow:sampled']['logContext'], + logContext: LogEvents['profile:follow']['logContext'], profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, ) { const {currentAccount} = useSession() @@ -308,7 +308,7 @@ function useProfileFollowMutation( ownProfile = findProfileQueryData(queryClient, currentAccount.did) } captureAction(ProgressGuideAction.Follow) - logEvent('profile:follow:sampled', { + logEvent('profile:follow', { logContext, didBecomeMutual: profile.viewer ? Boolean(profile.viewer.followedBy) @@ -322,12 +322,12 @@ function useProfileFollowMutation( } function useProfileUnfollowMutation( - logContext: LogEvents['profile:unfollow:sampled']['logContext'], + logContext: LogEvents['profile:unfollow']['logContext'], ) { const agent = useAgent() return useMutation<void, Error, {did: string; followUri: string}>({ mutationFn: async ({followUri}) => { - logEvent('profile:unfollow:sampled', {logContext}) + logEvent('profile:unfollow', {logContext}) return await agent.deleteFollow(followUri) }, }) @@ -409,7 +409,7 @@ function useProfileUnmuteMutation() { } export function useProfileBlockMutationQueue( - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, + profile: Shadow<AppBskyActorDefs.ProfileViewBasic>, ) { const queryClient = useQueryClient() const did = profile.did |