diff --git a/src/state/queries/usePostThread/index.ts b/src/state/queries/usePostThread/index.ts index bd1fa512fd..68c413cf95 100644 --- a/src/state/queries/usePostThread/index.ts +++ b/src/state/queries/usePostThread/index.ts @@ -74,6 +74,7 @@ export function usePostThread({ const items = flatten( sort(query.data?.thread || [], { + view: params.view, threadgateHiddenReplies: mergeThreadgateHiddenReplies( query.data?.threadgate?.record, ), @@ -85,6 +86,7 @@ export function usePostThread({ showHidden: state.shownHiddenReplyKinds.has(HiddenReplyKind.Hidden), }, ) + console.log(items) const mutator = createCacheMutator({ queryKey, diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index 013b17252b..ffe749b50e 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -1,10 +1,11 @@ import { + AtUri, AppBskyUnspeccedGetPostThreadV2, type ModerationDecision, type ModerationOpts, } from '@atproto/api' -import {HiddenReplyKind,type Slice} from '#/state/queries/usePostThread/types' +import {HiddenReplyKind, type PostThreadParams, type Slice} from '#/state/queries/usePostThread/types' import * as views from '#/state/queries/usePostThread/views' export function flatten( @@ -58,12 +59,13 @@ export function flatten( if (hasSession) { for (let i = 0; i < flattened.length; i++) { const item = flattened[i] - if ('depth' in item && item.depth === 0) { + + // TODO should not insert if not found post etc + if (item.type === 'threadPost' && item.depth === 0) { flattened.splice(i + 1, 0, { type: 'replyComposer', key: 'replyComposer', }) - break } } } @@ -74,9 +76,11 @@ export function flatten( export function sort( thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'], { + view, threadgateHiddenReplies, moderationOpts, }: { + view: PostThreadParams['view'] threadgateHiddenReplies: Set moderationOpts: ModerationOpts }, @@ -181,29 +185,97 @@ export function sort( const isFirstReply = lastSlice.type === 'replyComposer' || (lastSlice.type === 'threadPost' && lastSlice.depth === 0) - const parent = views.threadPost({ + const oneUp = isFirstReply ? undefined : thread.at(i - 1) + const oneDown = thread.at(i + 1) + const post = views.threadPost({ uri: item.uri, depth: item.depth, value: item.value, - oneUp: isFirstReply ? undefined : thread[i - 1], - oneDown: thread[i + 1], + oneUp, + oneDown, moderationOpts, }) - const parentMod = getModerationState(parent.moderation) - const parentIsHidden = threadgateHiddenReplies.has(item.uri) - const parentIsTopLevelReply = item.depth === 1 - const parentIsModerated = - parentIsHidden || parentMod.blurred || parentMod.muted + const postMod = getModerationState(post.moderation) + const postIsHidden = threadgateHiddenReplies.has(item.uri) + const postIsModerated = + postIsHidden || postMod.blurred || postMod.muted - if (!parentIsModerated) { + if (!postIsModerated) { /* - * Not hidden, so show it + * Not moderated, probably need to insert it */ - items.push(parent) + // 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 { - const branch = getBranch(thread, i, item.depth) + /* + * Moderated in some way, we're going to walk children + */ + const parent = post + const parentMod = postMod + const parentIsTopLevelReply = parent.depth === 1 const sortArray = parentMod.muted ? muted : hidden + // get sub tree + const branch = getBranch(thread, i, item.depth) + if (parentIsTopLevelReply) { // push branch anchor into sorted array sortArray.push(parent) @@ -302,6 +374,29 @@ function getBranch( } } +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, + } +} + export function getModerationState(moderation: ModerationDecision) { const modui = moderation.ui('contentList') const blurred = modui.blur || modui.filter diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts index 13d561c223..e06940f705 100644 --- a/src/state/queries/usePostThread/types.ts +++ b/src/state/queries/usePostThread/types.ts @@ -80,3 +80,8 @@ export type Slice = key: string kind: HiddenReplyKind } + | { + type: 'threadPostNoOp' + key: string + comment: string + } diff --git a/src/state/queries/usePostThread/views.ts b/src/state/queries/usePostThread/views.ts index dd0f083008..7450e6d83b 100644 --- a/src/state/queries/usePostThread/views.ts +++ b/src/state/queries/usePostThread/views.ts @@ -98,6 +98,8 @@ export function threadPost({ } } +// export function threadPostNoOp({ }) + export function postViewToThreadPlaceholder( post: AppBskyFeedDefs.PostView, ): $Typed<