From f8d5ad88c5284397cc721ea61be70d43078f3d83 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 20 May 2025 15:43:13 -0500 Subject: [PATCH] Working optimistic reply insertions, preference for OP --- src/state/queries/usePostThread/queryCache.ts | 52 +++++++---- src/state/queries/usePostThread/traversal.ts | 93 +------------------ src/state/queries/usePostThread/utils.ts | 16 ++++ 3 files changed, 56 insertions(+), 105 deletions(-) diff --git a/src/state/queries/usePostThread/queryCache.ts b/src/state/queries/usePostThread/queryCache.ts index c7d39b31bf..3fc4e160bf 100644 --- a/src/state/queries/usePostThread/queryCache.ts +++ b/src/state/queries/usePostThread/queryCache.ts @@ -6,11 +6,13 @@ import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/ import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed' import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes' import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts' +import {getBranch} from '#/state/queries/usePostThread/traversal' import { type createPostThreadQueryKey, - postThreadQueryKeyRoot, type PostThreadParams, + postThreadQueryKeyRoot, } from '#/state/queries/usePostThread/types' +import {getRootPostAtUri} from '#/state/queries/usePostThread/utils' import { embedViewToThreadPlaceholder, postViewToThreadPlaceholder, @@ -58,31 +60,49 @@ export function createCacheMutator({ replyCount: parent.value.post.replyCount, } + const opDid = getRootPostAtUri(existingParent.value.post)?.host const nextItem = thread.at(i + 1) const isReplyToRoot = existingParent.depth === 0 const isEndOfReplyChain = !nextItem || nextItem.depth <= existingParent.depth - const shouldInsertReplies = + const firstReply = replies.at(0) + const opIsReplier = + AppBskyUnspeccedGetPostThreadV2.isThreadItemPost( + firstReply?.value, + ) + ? opDid === firstReply.value.post.author.did + : false + + /* + * Always insert replies if the following conditions are met. + */ + const shouldAlwaysInsertReplies = isReplyToRoot || params.view === 'tree' || (params.view === 'linear' && isEndOfReplyChain) + /* + * Maybe insert replies if the replier is the OP and certain conditions are met + */ + const shouldReplaceWithOPReplies = + !isReplyToRoot && params.view === 'linear' && opIsReplier - if (shouldInsertReplies) { + if (shouldAlwaysInsertReplies || shouldReplaceWithOPReplies) { + const branch = getBranch(thread, i, existingParent.depth) /* - * Splice in new replies + * OP insertions replace other replies _in linear view_. */ - for (let ri = 0; ri < replies.length; ri++) { - const reply = replies[ri] - if ( - !AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(reply.value) - ) - continue - const insertIndex = i + 1 + ri - thread.splice(insertIndex, 0, { - ...reply, - depth: existingParent.depth + 1 + ri, - }) - } + const itemsToRemove = shouldReplaceWithOPReplies + ? branch.length + : 0 + + thread.splice( + i + 1, + itemsToRemove, + ...replies.map((r, ri) => { + r.depth = existingParent.depth + 1 + ri + return r + }), + ) } } diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index d8eb14d7a0..f614ed7cdf 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -1,11 +1,10 @@ import { - AtUri, AppBskyUnspeccedGetPostThreadV2, type ModerationDecision, type ModerationOpts, } from '@atproto/api' -import {HiddenReplyKind, type PostThreadParams, type Slice} from '#/state/queries/usePostThread/types' +import {HiddenReplyKind, type Slice} from '#/state/queries/usePostThread/types' import * as views from '#/state/queries/usePostThread/views' export function flatten( @@ -76,11 +75,9 @@ export function flatten( export function sort( thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'], { - view, threadgateHiddenReplies, moderationOpts, }: { - view: PostThreadParams['view'] threadgateHiddenReplies: Set moderationOpts: ModerationOpts }, @@ -197,73 +194,13 @@ export function sort( }) const postMod = getModerationState(post.moderation) const postIsHidden = threadgateHiddenReplies.has(item.uri) - const postIsModerated = - postIsHidden || postMod.blurred || postMod.muted + const postIsModerated = postIsHidden || postMod.blurred || postMod.muted if (!postIsModerated) { /* * Not moderated, probably need to insert it */ items.push(post) - // if (view === 'tree') { - // items.push(post) - // } else { - // if (post.depth > 1) { - // const maybeNextSiblingIndex = getBranch(thread, i, post.depth).end + 1 - // const maybeNextSibling = thread.at(maybeNextSiblingIndex) - - // /* - // * If we've got two replies at the same depth, we need to decide - // * which one to show. - // */ - // if (post.depth === maybeNextSibling?.depth) { - // // continue with next sibling - // i = maybeNextSiblingIndex - 1 - // debugger; - // continue traversal - // } else { - // const maybePrevSiblingIndex = getBranchUp(thread, i - 1, post.depth).end - // const maybePrevSibling = thread.at(maybePrevSiblingIndex) - - // if (post.depth === maybePrevSibling?.depth) { - // const post = views.threadPost({ - // uri: item.uri, - // depth: item.depth, - // value: item.value, - // oneUp: thread.at(maybePrevSiblingIndex - 1), - // oneDown, - // moderationOpts, - // }) - // debugger; - // items.push(post) - // } else { - // items.push(post) - // } - // } - - // /* - // if (post.depth === oneDown?.depth) { - // const opDid = new AtUri(post.value.post.record.reply?.root?.uri).host - // const postAuthorDid = new AtUri(post.uri).host - // debugger; - // if (postAuthorDid === opDid) { - // // prioritize OP optimistic reply - // items.push(post) - // // skip next reply - // i = getBranch(thread, i, oneDown.depth).end - // debugger; - // continue traversal - // } else { - // i = getBranch(thread, i, post.depth).end - // debugger; - // continue traversal - // } - // } - // */ - // } else { - // items.push(post) - // } - // } } else { /* * Moderated in some way, we're going to walk children @@ -351,7 +288,7 @@ export function sort( * * const { start: 1, end: 3 } = getBranch(items, 1, 1) */ -function getBranch( +export function getBranch( thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'], branchStartIndex: number, branchStartDepth: number, @@ -371,29 +308,7 @@ function getBranch( return { start: branchStartIndex, end, - } -} - -function getBranchUp( - thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'], - branchStartIndex: number, - branchEndDepth: number, -) { - let start = branchStartIndex - - for (let ci = branchStartIndex; ci >= 0; ci--) { - const next = thread[ci] - if (next.depth > branchEndDepth) { - start = ci - } else { - start = ci - break - } - } - - return { - start, - end: branchStartIndex, + length: end - branchStartIndex, } } diff --git a/src/state/queries/usePostThread/utils.ts b/src/state/queries/usePostThread/utils.ts index 2436cecba0..daef2e7d89 100644 --- a/src/state/queries/usePostThread/utils.ts +++ b/src/state/queries/usePostThread/utils.ts @@ -1,7 +1,10 @@ import { APP_BSKY_UNSPECCED, + type AppBskyFeedDefs, + AppBskyFeedPost, AppBskyFeedThreadgate, type AppBskyUnspeccedGetPostThreadV2, + AtUri, } from '@atproto/api' import {type PostThreadParams} from '#/state/queries/usePostThread/types' @@ -32,3 +35,16 @@ export function getThreadgateRecord( ? view?.record : undefined } + +export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) { + if ( + bsky.dangerousIsType( + post.record, + AppBskyFeedPost.isRecord, + ) + ) { + if (post.record.reply?.root?.uri) { + return new AtUri(post.record.reply.root.uri) + } + } +}