Little cleanup, inserting dupes

This commit is contained in:
Eric Bailey
2025-05-17 10:30:12 -07:00
parent 4a83579795
commit e82627cf4f
3 changed files with 64 additions and 58 deletions
+57 -53
View File
@@ -3,15 +3,16 @@ import {
$Typed, $Typed,
AtUri, AtUri,
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyUnspeccedDefs,
AppBskyFeedThreadgate, AppBskyFeedThreadgate,
AppBskyFeedGetPostThreadV2, AppBskyUnspeccedGetPostThreadV2,
ModerationOpts, ModerationOpts,
BskyThreadViewPreference, BskyThreadViewPreference,
moderatePost, moderatePost,
ModerationDecision, ModerationDecision,
AppBskyEmbedRecord, AppBskyEmbedRecord,
AppBskyFeedPost, AppBskyFeedPost,
APP_BSKY_FEED, APP_BSKY_UNSPECCED,
} from '@atproto/api' } from '@atproto/api'
import {useQuery, useQueryClient, QueryClient} from '@tanstack/react-query' import {useQuery, useQueryClient, QueryClient} from '@tanstack/react-query'
@@ -71,15 +72,15 @@ export type GetPostThreadV2QueryData = {
export function mapSortOptionsToSortID(sort: PostThreadV2Params['sort']) { export function mapSortOptionsToSortID(sort: PostThreadV2Params['sort']) {
switch (sort) { switch (sort) {
case 'hotness': case 'hotness':
return APP_BSKY_FEED.GetPostThreadV2Hotness return APP_BSKY_UNSPECCED.GetPostThreadV2Hotness
case 'oldest': case 'oldest':
return APP_BSKY_FEED.GetPostThreadV2Oldest return APP_BSKY_UNSPECCED.GetPostThreadV2Oldest
case 'newest': case 'newest':
return APP_BSKY_FEED.GetPostThreadV2Newest return APP_BSKY_UNSPECCED.GetPostThreadV2Newest
case 'most-likes': case 'most-likes':
return APP_BSKY_FEED.GetPostThreadV2MostLikes return APP_BSKY_UNSPECCED.GetPostThreadV2MostLikes
default: default:
return APP_BSKY_FEED.GetPostThreadV2Hotness return APP_BSKY_UNSPECCED.GetPostThreadV2Hotness
} }
} }
@@ -104,7 +105,7 @@ export function useGetPostThreadV2({
params, params,
}), }),
async queryFn() { async queryFn() {
const {data} = await agent.app.bsky.feed.getPostThreadV2({ const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
uri: uri!, uri: uri!,
branchingFactor: params.view === 'linear' ? 1 : 10, branchingFactor: params.view === 'linear' ? 1 : 10,
below: 10, below: 10,
@@ -163,7 +164,7 @@ const views = {
noUnauthenticated({ noUnauthenticated({
item, item,
}: { }: {
item: AppBskyFeedDefs.ThreadItemNoUnauthenticated item: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated
}): Extract<Slice, {type: 'threadSliceNoUnauthenticated'}> { }): Extract<Slice, {type: 'threadSliceNoUnauthenticated'}> {
return { return {
type: 'threadSliceNoUnauthenticated', type: 'threadSliceNoUnauthenticated',
@@ -174,7 +175,7 @@ const views = {
notFound({ notFound({
item, item,
}: { }: {
item: AppBskyFeedDefs.ThreadItemNotFound item: AppBskyUnspeccedDefs.ThreadItemNotFound
}): Extract<Slice, {type: 'threadSliceNotFound'}> { }): Extract<Slice, {type: 'threadSliceNotFound'}> {
return { return {
type: 'threadSliceNotFound', type: 'threadSliceNotFound',
@@ -185,7 +186,7 @@ const views = {
blocked({ blocked({
item, item,
}: { }: {
item: AppBskyFeedDefs.ThreadItemBlocked item: AppBskyUnspeccedDefs.ThreadItemBlocked
}): Extract<Slice, {type: 'threadSliceBlocked'}> { }): Extract<Slice, {type: 'threadSliceBlocked'}> {
return { return {
type: 'threadSliceBlocked', type: 'threadSliceBlocked',
@@ -199,9 +200,9 @@ const views = {
oneDown, oneDown,
moderationOpts, moderationOpts,
}: { }: {
item: AppBskyFeedDefs.ThreadItemPost item: AppBskyUnspeccedDefs.ThreadItemPost
oneUp?: AppBskyFeedGetPostThreadV2.OutputSchema['thread'][number] oneUp?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
oneDown?: AppBskyFeedGetPostThreadV2.OutputSchema['thread'][number] oneDown?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}): Extract<Slice, {type: 'threadSlice'}> { }): Extract<Slice, {type: 'threadSlice'}> {
return { return {
@@ -243,7 +244,7 @@ const views = {
* const { start: 1, end: 3 } = getBranch(items, 1, 1) * const { start: 1, end: 3 } = getBranch(items, 1, 1)
*/ */
function getBranch( function getBranch(
thread: AppBskyFeedGetPostThreadV2.OutputSchema['thread'], thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'],
branchStartIndex: number, branchStartIndex: number,
branchStartDepth: number, branchStartDepth: number,
) { ) {
@@ -268,7 +269,7 @@ function getBranch(
} }
export function useThread( export function useThread(
thread: AppBskyFeedGetPostThreadV2.OutputSchema['thread'], thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'],
{ {
hasSession, hasSession,
params, params,
@@ -285,9 +286,9 @@ export function useThread(
) { ) {
const isTreeView = params.view === 'tree' const isTreeView = params.view === 'tree'
const [shadowSlices, setShadowSlices] = useState< const [shadowSlices, setShadowSlices] = useState<
Record<string, AppBskyFeedDefs.ThreadItemPost[][]> Record<string, AppBskyUnspeccedDefs.ThreadItemPost[][]>
>({}) >({})
const insertReplies = useCallback((belowUri: string, posts: AppBskyFeedDefs.ThreadItemPost[]) => { const insertReplies = useCallback((belowUri: string, posts: AppBskyUnspeccedDefs.ThreadItemPost[]) => {
setShadowSlices(p => { setShadowSlices(p => {
const prev = {...p} const prev = {...p}
if (belowUri in prev) { if (belowUri in prev) {
@@ -354,6 +355,7 @@ export function useThread(
if (!(item.slice.uri in shadowSlices)) continue if (!(item.slice.uri in shadowSlices)) continue
const replyThreads = shadowSlices[item.slice.uri] const replyThreads = shadowSlices[item.slice.uri]
console.log(replyThreads)
// TODO linear view will work // TODO linear view will work
if (!isTreeView) continue if (!isTreeView) continue
@@ -375,17 +377,19 @@ export function useThread(
flattened[insertIndex]?.key === view.key ? 1 : 0, flattened[insertIndex]?.key === view.key ? 1 : 0,
view view
) )
// console.log('insert', { console.log('insert', {
// post: post.post.record?.text, post: post.post.record?.text,
// depth: item.slice.depth + 1 + ri, depth: item.slice.depth + 1 + ri,
// ri, ri,
// spliceIndex: i + 1 + ri, spliceIndex: i + 1 + ri,
// }) tree: flattened.map(f => f.slice?.post?.record?.text),
// console.log(flattened.map(f => f.slice?.post?.record?.text)) })
} }
// TODO may not need since inserts will be looped and ignored // TODO may not need since inserts will be looped and ignored
i = i + thread.length // console.log('prev', i)
// i = i - thread.length
// console.log('next', i)
} }
} }
@@ -419,7 +423,7 @@ export function useThread(
} }
export function sortThread( export function sortThread(
thread: AppBskyFeedGetPostThreadV2.OutputSchema['thread'], thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'],
{ {
threadgateHiddenReplies, threadgateHiddenReplies,
moderationOpts, moderationOpts,
@@ -444,13 +448,13 @@ export function sortThread(
* _up_ from there. * _up_ from there.
*/ */
} else if (item.depth === 0) { } else if (item.depth === 0) {
if (AppBskyFeedDefs.isThreadItemNoUnauthenticated(item)) { if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item)) {
slices.push(views.noUnauthenticated({item})) slices.push(views.noUnauthenticated({item}))
} else if (AppBskyFeedDefs.isThreadItemNotFound(item)) { } else if (AppBskyUnspeccedDefs.isThreadItemNotFound(item)) {
slices.push(views.notFound({item})) slices.push(views.notFound({item}))
} else if (AppBskyFeedDefs.isThreadItemBlocked(item)) { } else if (AppBskyUnspeccedDefs.isThreadItemBlocked(item)) {
slices.push(views.blocked({item})) slices.push(views.blocked({item}))
} else if (AppBskyFeedDefs.isThreadItemPost(item)) { } else if (AppBskyUnspeccedDefs.isThreadItemPost(item)) {
slices.push( slices.push(
views.post({ views.post({
item, item,
@@ -465,16 +469,16 @@ export function sortThread(
const parent = thread[pi] const parent = thread[pi]
const parentOneUp = thread[pi - 1] const parentOneUp = thread[pi - 1]
if (AppBskyFeedDefs.isThreadItemNoUnauthenticated(parent)) { if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(parent)) {
slices.unshift(views.noUnauthenticated({item: parent})) slices.unshift(views.noUnauthenticated({item: parent}))
break parentTraversal break parentTraversal
} else if (AppBskyFeedDefs.isThreadItemNotFound(parent)) { } else if (AppBskyUnspeccedDefs.isThreadItemNotFound(parent)) {
slices.unshift(views.notFound({item: parent})) slices.unshift(views.notFound({item: parent}))
break parentTraversal break parentTraversal
} else if (AppBskyFeedDefs.isThreadItemBlocked(parent)) { } else if (AppBskyUnspeccedDefs.isThreadItemBlocked(parent)) {
slices.unshift(views.blocked({item: parent})) slices.unshift(views.blocked({item: parent}))
break parentTraversal break parentTraversal
} else if (AppBskyFeedDefs.isThreadItemPost(parent)) { } else if (AppBskyUnspeccedDefs.isThreadItemPost(parent)) {
slices.unshift( slices.unshift(
views.post({ views.post({
item: parent, item: parent,
@@ -493,16 +497,16 @@ export function sortThread(
* we could. * we could.
*/ */
const shouldBreak = const shouldBreak =
AppBskyFeedDefs.isThreadItemNoUnauthenticated(item) || AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item) ||
AppBskyFeedDefs.isThreadItemNotFound(item) || AppBskyUnspeccedDefs.isThreadItemNotFound(item) ||
AppBskyFeedDefs.isThreadItemBlocked(item) AppBskyUnspeccedDefs.isThreadItemBlocked(item)
if (shouldBreak) { if (shouldBreak) {
const branch = getBranch(thread, i, item.depth) const branch = getBranch(thread, i, item.depth)
// could insert tombstone // could insert tombstone
i = branch.end i = branch.end
continue traversal continue traversal
} else if (AppBskyFeedDefs.isThreadItemPost(item)) { } else if (AppBskyUnspeccedDefs.isThreadItemPost(item)) {
const lastSlice = slices[slices.length - 1] const lastSlice = slices[slices.length - 1]
const isFirstReply = const isFirstReply =
lastSlice.type === 'replyComposer' || lastSlice.type === 'replyComposer' ||
@@ -537,7 +541,7 @@ export function sortThread(
for (let ci = startIndex; ci <= branch.end; ci++) { for (let ci = startIndex; ci <= branch.end; ci++) {
const child = thread[ci] const child = thread[ci]
if (AppBskyFeedDefs.isThreadItemPost(child)) { if (AppBskyUnspeccedDefs.isThreadItemPost(child)) {
const childPost = views.post({ const childPost = views.post({
item: child, item: child,
oneUp: thread[ci - 1], oneUp: thread[ci - 1],
@@ -602,7 +606,7 @@ export type Slice =
| { | {
type: 'threadSlice' type: 'threadSlice'
key: string key: string
slice: Omit<AppBskyFeedDefs.ThreadItemPost, 'post'> & { slice: Omit<AppBskyUnspeccedDefs.ThreadItemPost, 'post'> & {
post: Omit<AppBskyFeedDefs.PostView, 'record'> & { post: Omit<AppBskyFeedDefs.PostView, 'record'> & {
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
} }
@@ -617,17 +621,17 @@ export type Slice =
| { | {
type: 'threadSliceNoUnauthenticated' type: 'threadSliceNoUnauthenticated'
key: string key: string
slice: AppBskyFeedDefs.ThreadItemNoUnauthenticated slice: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated
} }
| { | {
type: 'threadSliceNotFound' type: 'threadSliceNotFound'
key: string key: string
slice: AppBskyFeedDefs.ThreadItemNotFound slice: AppBskyUnspeccedDefs.ThreadItemNotFound
} }
| { | {
type: 'threadSliceBlocked' type: 'threadSliceBlocked'
key: string key: string
slice: AppBskyFeedDefs.ThreadItemBlocked slice: AppBskyUnspeccedDefs.ThreadItemBlocked
} }
| { | {
type: 'replyComposer' type: 'replyComposer'
@@ -640,7 +644,7 @@ export type Slice =
} }
function getThreadgate( function getThreadgate(
view: AppBskyFeedGetPostThreadV2.OutputSchema['threadgate'], view: AppBskyUnspeccedGetPostThreadV2.OutputSchema['threadgate'],
) { ) {
return bsky.dangerousIsType<AppBskyFeedThreadgate.Record>( return bsky.dangerousIsType<AppBskyFeedThreadgate.Record>(
view?.record, view?.record,
@@ -653,7 +657,7 @@ function getThreadgate(
function getSlicePlaceholder( function getSlicePlaceholder(
queryClient: QueryClient, queryClient: QueryClient,
uri: string, uri: string,
): $Typed<AppBskyFeedDefs.ThreadItemPost> | void { ): $Typed<AppBskyUnspeccedDefs.ThreadItemPost> | void {
let partial let partial
for (let item of yieldPlaceholdersFromQueryCache(queryClient, uri)) { for (let item of yieldPlaceholdersFromQueryCache(queryClient, uri)) {
/* /*
@@ -678,7 +682,7 @@ function getSlicePlaceholder(
export function* yieldPlaceholdersFromQueryCache( export function* yieldPlaceholdersFromQueryCache(
queryClient: QueryClient, queryClient: QueryClient,
uri: string, uri: string,
): Generator<$Typed<AppBskyFeedDefs.ThreadItemPost>, void> { ): Generator<$Typed<AppBskyUnspeccedDefs.ThreadItemPost>, void> {
const atUri = new AtUri(uri) const atUri = new AtUri(uri)
/* /*
@@ -686,7 +690,7 @@ export function* yieldPlaceholdersFromQueryCache(
* TODO extract just this for shadowing * TODO extract just this for shadowing
*/ */
const queryDatas = const queryDatas =
queryClient.getQueriesData<AppBskyFeedGetPostThreadV2.OutputSchema>({ queryClient.getQueriesData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>({
queryKey: [getPostThreadV2QueryKeyRoot], queryKey: [getPostThreadV2QueryKeyRoot],
}) })
for (const [_queryKey, queryData] of queryDatas) { for (const [_queryKey, queryData] of queryDatas) {
@@ -695,7 +699,7 @@ export function* yieldPlaceholdersFromQueryCache(
const {thread} = queryData const {thread} = queryData
for (const item of thread) { for (const item of thread) {
if (AppBskyFeedDefs.isThreadItemPost(item)) { if (AppBskyUnspeccedDefs.isThreadItemPost(item)) {
if (didOrHandleUriMatches(atUri, item.post)) { if (didOrHandleUriMatches(atUri, item.post)) {
yield { yield {
...item, ...item,
@@ -739,9 +743,9 @@ export function* yieldPlaceholdersFromQueryCache(
function postViewToSlicePlaceholder( function postViewToSlicePlaceholder(
post: AppBskyFeedDefs.PostView, post: AppBskyFeedDefs.PostView,
): $Typed<AppBskyFeedDefs.ThreadItemPost> { ): $Typed<AppBskyUnspeccedDefs.ThreadItemPost> {
return { return {
$type: 'app.bsky.feed.defs#threadItemPost', $type: 'app.bsky.unspecced.defs#threadItemPost',
uri: post.uri, uri: post.uri,
post, post,
depth: 0, // reset to 0 for highlighted post depth: 0, // reset to 0 for highlighted post
@@ -755,9 +759,9 @@ function postViewToSlicePlaceholder(
function embedViewToSlicePlaceholder( function embedViewToSlicePlaceholder(
record: AppBskyEmbedRecord.ViewRecord, record: AppBskyEmbedRecord.ViewRecord,
): $Typed<AppBskyFeedDefs.ThreadItemPost> { ): $Typed<AppBskyUnspeccedDefs.ThreadItemPost> {
return { return {
$type: 'app.bsky.feed.defs#threadItemPost', $type: 'app.bsky.unspecced.defs#threadItemPost',
uri: record.uri, uri: record.uri,
post: embedViewRecordToPostView(record), post: embedViewRecordToPostView(record),
depth: 0, // reset to 0 for highlighted post depth: 0, // reset to 0 for highlighted post
+4 -2
View File
@@ -44,6 +44,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {type ImagePickerAsset} from 'expo-image-picker' import {type ImagePickerAsset} from 'expo-image-picker'
import { import {
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyUnspeccedDefs,
type AppBskyFeedGetPostThread, type AppBskyFeedGetPostThread,
type BskyAgent, type BskyAgent,
type RichText, type RichText,
@@ -404,16 +405,17 @@ export const ComposePost = ({
try { try {
if (postUri) { if (postUri) {
posts = await retry(5, _e => true, async () => { posts = await retry(5, _e => true, async () => {
const res = await agent.app.bsky.feed.getPostThreadV2({ const res = await agent.app.bsky.unspecced.getPostThreadV2({
uri: postUri!, uri: postUri!,
above: 0, above: 0,
below: thread.posts.length - 1, below: thread.posts.length - 1,
branchingFactor: 1,
}) })
if (res.data.thread.length !== thread.posts.length) { if (res.data.thread.length !== thread.posts.length) {
throw new Error(`Not ready`) throw new Error(`Not ready`)
} }
const anchor = res.data.thread.at(0) const anchor = res.data.thread.at(0)
if (!AppBskyFeedDefs.isThreadItemPost(anchor)) { if (!AppBskyUnspeccedDefs.isThreadItemPost(anchor)) {
throw new Error(`Not ready`) throw new Error(`Not ready`)
} }
return res.data.thread return res.data.thread
+3 -3
View File
@@ -3,7 +3,7 @@ import {StyleSheet, useWindowDimensions, View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyUnspeccedDefs} from '@atproto/api'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
@@ -149,8 +149,8 @@ export function Inner({uri}: {uri: string | undefined}) {
const optimisticOnPostReply = ({ const optimisticOnPostReply = ({
post, post,
}: { }: {
post: AppBskyFeedDefs.ThreadItemPost, post: AppBskyUnspeccedDefs.ThreadItemPost,
}) => (_: any, posts: AppBskyFeedDefs.ThreadItemPost[]) => { }) => (_: any, posts: AppBskyUnspeccedDefs.ThreadItemPost[]) => {
if (posts.length) { if (posts.length) {
// TODO get parent and update reply count? // TODO get parent and update reply count?
insertReplies(post.uri, posts) insertReplies(post.uri, posts)