Update types
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {getThreadPlaceholder, createCacheMutator} from '#/state/queries/usePostThread/queryCache'
|
import {
|
||||||
import {flatten,sort} from '#/state/queries/usePostThread/traversal'
|
createCacheMutator,
|
||||||
|
getThreadPlaceholder,
|
||||||
|
} from '#/state/queries/usePostThread/queryCache'
|
||||||
|
import {flatten, sort} from '#/state/queries/usePostThread/traversal'
|
||||||
import {
|
import {
|
||||||
createPostThreadQueryKey,
|
createPostThreadQueryKey,
|
||||||
HiddenReplyKind,
|
HiddenReplyKind,
|
||||||
@@ -41,7 +44,7 @@ export function usePostThread({
|
|||||||
async queryFn() {
|
async queryFn() {
|
||||||
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
|
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
|
||||||
uri: uri!,
|
uri: uri!,
|
||||||
branchingFactor: params.view === 'linear' ? 1 : 10,
|
nestedBranchingFactor: params.view === 'linear' ? 1 : 10,
|
||||||
below: 10,
|
below: 10,
|
||||||
sorting: mapSortOptionsToSortID(params.sort),
|
sorting: mapSortOptionsToSortID(params.sort),
|
||||||
})
|
})
|
||||||
@@ -91,7 +94,7 @@ export function usePostThread({
|
|||||||
return {
|
return {
|
||||||
...query,
|
...query,
|
||||||
data: {
|
data: {
|
||||||
slices: items,
|
items,
|
||||||
threadgate: query.data?.threadgate,
|
threadgate: query.data?.threadgate,
|
||||||
},
|
},
|
||||||
insertReplies: mutator.insertReplies,
|
insertReplies: mutator.insertReplies,
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import {type $Typed, AppBskyUnspeccedGetPostThreadV2, AtUri} from '@atproto/api'
|
||||||
type $Typed,
|
|
||||||
AppBskyUnspeccedDefs,
|
|
||||||
type AppBskyUnspeccedGetPostThreadV2,
|
|
||||||
AtUri,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {type QueryClient} from '@tanstack/react-query'
|
import {type QueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} from '#/state/queries/explore-feed-previews'
|
import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} from '#/state/queries/explore-feed-previews'
|
||||||
@@ -12,8 +7,8 @@ import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/qu
|
|||||||
import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes'
|
import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes'
|
||||||
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts'
|
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts'
|
||||||
import {
|
import {
|
||||||
|
type createPostThreadQueryKey,
|
||||||
postThreadQueryKeyRoot,
|
postThreadQueryKeyRoot,
|
||||||
createPostThreadQueryKey,
|
|
||||||
} from '#/state/queries/usePostThread/types'
|
} from '#/state/queries/usePostThread/types'
|
||||||
import {
|
import {
|
||||||
embedViewToThreadPlaceholder,
|
embedViewToThreadPlaceholder,
|
||||||
@@ -30,8 +25,8 @@ export function createCacheMutator({
|
|||||||
}) {
|
}) {
|
||||||
return {
|
return {
|
||||||
insertReplies(
|
insertReplies(
|
||||||
parent: AppBskyUnspeccedDefs.ThreadItemPost,
|
parent: AppBskyUnspeccedGetPostThreadV2.ThreadItem,
|
||||||
replies: AppBskyUnspeccedDefs.ThreadItemPost[],
|
replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[],
|
||||||
) {
|
) {
|
||||||
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
|
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
|
||||||
queryKey,
|
queryKey,
|
||||||
@@ -41,16 +36,23 @@ export function createCacheMutator({
|
|||||||
const thread = [...queryData.thread]
|
const thread = [...queryData.thread]
|
||||||
|
|
||||||
for (let i = 0; i < thread.length; i++) {
|
for (let i = 0; i < thread.length; i++) {
|
||||||
const anchor = thread[i]
|
const existingParent = thread[i]
|
||||||
if (!AppBskyUnspeccedDefs.isThreadItemPost(anchor)) continue
|
if (
|
||||||
if (anchor.uri !== parent.uri) continue
|
!AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(
|
||||||
|
existingParent.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if (!AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(parent.value))
|
||||||
|
continue
|
||||||
|
if (existingParent.uri !== parent.uri) continue
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update parent data
|
* Update parent data
|
||||||
*/
|
*/
|
||||||
anchor.post = {
|
existingParent.value.post = {
|
||||||
...anchor.post,
|
...existingParent.value.post,
|
||||||
replyCount: parent.post.replyCount,
|
replyCount: parent.value.post.replyCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -58,11 +60,14 @@ export function createCacheMutator({
|
|||||||
*/
|
*/
|
||||||
for (let ri = 0; ri < replies.length; ri++) {
|
for (let ri = 0; ri < replies.length; ri++) {
|
||||||
const reply = replies[ri]
|
const reply = replies[ri]
|
||||||
reply.depth = anchor.depth + 1 + ri
|
if (
|
||||||
|
!AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(reply.value)
|
||||||
|
)
|
||||||
|
continue
|
||||||
const insertIndex = i + 1 + ri
|
const insertIndex = i + 1 + ri
|
||||||
thread.splice(insertIndex, 0, {
|
thread.splice(insertIndex, 0, {
|
||||||
$type: 'app.bsky.unspecced.defs#threadItemPost',
|
|
||||||
...reply,
|
...reply,
|
||||||
|
depth: existingParent.depth + 1 + ri,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,14 +79,14 @@ export function createCacheMutator({
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
deletePost(post: AppBskyUnspeccedDefs.ThreadItemPost) {},
|
deletePost(_post: AppBskyUnspeccedGetPostThreadV2.ThreadItem) {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getThreadPlaceholder(
|
export function getThreadPlaceholder(
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
uri: string,
|
uri: string,
|
||||||
): $Typed<AppBskyUnspeccedDefs.ThreadItemPost> | void {
|
): $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItem> | void {
|
||||||
let partial
|
let partial
|
||||||
for (let item of getThreadPlaceholderCandidates(queryClient, uri)) {
|
for (let item of getThreadPlaceholderCandidates(queryClient, uri)) {
|
||||||
/*
|
/*
|
||||||
@@ -92,7 +97,7 @@ export function getThreadPlaceholder(
|
|||||||
*
|
*
|
||||||
* TODO can we send in feeds and quotes?
|
* TODO can we send in feeds and quotes?
|
||||||
*/
|
*/
|
||||||
const hasAllInfo = item.post.likeCount != null
|
const hasAllInfo = item.value.post.likeCount != null
|
||||||
if (hasAllInfo) {
|
if (hasAllInfo) {
|
||||||
return item
|
return item
|
||||||
} else {
|
} else {
|
||||||
@@ -106,7 +111,14 @@ export function getThreadPlaceholder(
|
|||||||
export function* getThreadPlaceholderCandidates(
|
export function* getThreadPlaceholderCandidates(
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
uri: string,
|
uri: string,
|
||||||
): Generator<$Typed<AppBskyUnspeccedDefs.ThreadItemPost>, void> {
|
): Generator<
|
||||||
|
$Typed<
|
||||||
|
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
|
||||||
|
value: $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost>
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
void
|
||||||
|
> {
|
||||||
const atUri = new AtUri(uri)
|
const atUri = new AtUri(uri)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -123,15 +135,17 @@ export function* getThreadPlaceholderCandidates(
|
|||||||
const {thread} = queryData
|
const {thread} = queryData
|
||||||
|
|
||||||
for (const item of thread) {
|
for (const item of thread) {
|
||||||
if (AppBskyUnspeccedDefs.isThreadItemPost(item)) {
|
if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) {
|
||||||
if (didOrHandleUriMatches(atUri, item.post)) {
|
if (didOrHandleUriMatches(atUri, item.value.post)) {
|
||||||
yield {
|
yield {
|
||||||
|
$type: 'app.bsky.unspecced.getPostThreadV2#threadItem',
|
||||||
...item,
|
...item,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
|
value: item.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const qp = getEmbeddedPost(item.post.embed)
|
const qp = getEmbeddedPost(item.value.post.embed)
|
||||||
if (qp && didOrHandleUriMatches(atUri, qp)) {
|
if (qp && didOrHandleUriMatches(atUri, qp)) {
|
||||||
yield embedViewToThreadPlaceholder(qp)
|
yield embedViewToThreadPlaceholder(qp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
AppBskyUnspeccedDefs,
|
AppBskyUnspeccedGetPostThreadV2,
|
||||||
type AppBskyUnspeccedGetPostThreadV2,
|
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -20,7 +19,7 @@ export function flatten(
|
|||||||
showHidden: boolean
|
showHidden: boolean
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const flattened: Slice[] = sorted.slices
|
const flattened: Slice[] = sorted.items
|
||||||
|
|
||||||
if (sorted.hidden.length) {
|
if (sorted.hidden.length) {
|
||||||
if (showHidden) {
|
if (showHidden) {
|
||||||
@@ -58,8 +57,8 @@ export function flatten(
|
|||||||
|
|
||||||
if (hasSession) {
|
if (hasSession) {
|
||||||
for (let i = 0; i < flattened.length; i++) {
|
for (let i = 0; i < flattened.length; i++) {
|
||||||
const slice = flattened[i]
|
const item = flattened[i]
|
||||||
if ('slice' in slice && slice.slice.depth === 0) {
|
if ('depth' in item && item.depth === 0) {
|
||||||
flattened.splice(i + 1, 0, {
|
flattened.splice(i + 1, 0, {
|
||||||
type: 'replyComposer',
|
type: 'replyComposer',
|
||||||
key: 'replyComposer',
|
key: 'replyComposer',
|
||||||
@@ -82,32 +81,39 @@ export function sort(
|
|||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const slices: Slice[] = []
|
const items: Slice[] = []
|
||||||
const hidden: Slice[] = []
|
const hidden: Slice[] = []
|
||||||
const muted: Slice[] = []
|
const muted: Slice[] = []
|
||||||
|
|
||||||
traversal: for (let i = 0; i < thread.length; i++) {
|
traversal: for (let i = 0; i < thread.length; i++) {
|
||||||
const item = thread[i]
|
const item = thread[i]
|
||||||
|
|
||||||
// ignore unknowns
|
|
||||||
if (!('depth' in item)) continue
|
|
||||||
|
|
||||||
if (item.depth < 0) {
|
if (item.depth < 0) {
|
||||||
/*
|
/*
|
||||||
* Parents are ignored until we find the highlighted post, then we walk
|
* Parents are ignored until we find the highlighted post, then we walk
|
||||||
* _up_ from there.
|
* _up_ from there.
|
||||||
*/
|
*/
|
||||||
} else if (item.depth === 0) {
|
} else if (item.depth === 0) {
|
||||||
if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item)) {
|
if (
|
||||||
slices.push(views.noUnauthenticated({item}))
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemNoUnauthenticated(
|
||||||
} else if (AppBskyUnspeccedDefs.isThreadItemNotFound(item)) {
|
item.value,
|
||||||
slices.push(views.notFound({item}))
|
)
|
||||||
} else if (AppBskyUnspeccedDefs.isThreadItemBlocked(item)) {
|
) {
|
||||||
slices.push(views.blocked({item}))
|
items.push(views.threadPostNoUnauthenticated(item))
|
||||||
} else if (AppBskyUnspeccedDefs.isThreadItemPost(item)) {
|
} else if (
|
||||||
slices.push(
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemNotFound(item.value)
|
||||||
views.post({
|
) {
|
||||||
item,
|
items.push(views.threadPostNotFound(item))
|
||||||
|
} else if (
|
||||||
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemBlocked(item.value)
|
||||||
|
) {
|
||||||
|
items.push(views.threadPostBlocked(item))
|
||||||
|
} else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) {
|
||||||
|
items.push(
|
||||||
|
views.threadPost({
|
||||||
|
uri: item.uri,
|
||||||
|
depth: item.depth,
|
||||||
|
value: item.value,
|
||||||
oneUp: thread[i - 1],
|
oneUp: thread[i - 1],
|
||||||
oneDown: thread[i + 1],
|
oneDown: thread[i + 1],
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
@@ -119,19 +125,31 @@ export function sort(
|
|||||||
const parent = thread[pi]
|
const parent = thread[pi]
|
||||||
const parentOneUp = thread[pi - 1]
|
const parentOneUp = thread[pi - 1]
|
||||||
|
|
||||||
if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(parent)) {
|
if (
|
||||||
slices.unshift(views.noUnauthenticated({item: parent}))
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemNoUnauthenticated(
|
||||||
|
parent.value,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
items.unshift(views.threadPostNoUnauthenticated(parent))
|
||||||
break parentTraversal
|
break parentTraversal
|
||||||
} else if (AppBskyUnspeccedDefs.isThreadItemNotFound(parent)) {
|
} else if (
|
||||||
slices.unshift(views.notFound({item: parent}))
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemNotFound(parent.value)
|
||||||
|
) {
|
||||||
|
items.unshift(views.threadPostNotFound(parent))
|
||||||
break parentTraversal
|
break parentTraversal
|
||||||
} else if (AppBskyUnspeccedDefs.isThreadItemBlocked(parent)) {
|
} else if (
|
||||||
slices.unshift(views.blocked({item: parent}))
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemBlocked(parent.value)
|
||||||
|
) {
|
||||||
|
items.unshift(views.threadPostBlocked(parent))
|
||||||
break parentTraversal
|
break parentTraversal
|
||||||
} else if (AppBskyUnspeccedDefs.isThreadItemPost(parent)) {
|
} else if (
|
||||||
slices.unshift(
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(parent.value)
|
||||||
views.post({
|
) {
|
||||||
item: parent,
|
items.unshift(
|
||||||
|
views.threadPost({
|
||||||
|
uri: parent.uri,
|
||||||
|
depth: parent.depth,
|
||||||
|
value: parent.value,
|
||||||
oneUp: parentOneUp,
|
oneUp: parentOneUp,
|
||||||
oneDown: parentOneDown,
|
oneDown: parentOneDown,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
@@ -147,22 +165,26 @@ export function sort(
|
|||||||
* we could.
|
* we could.
|
||||||
*/
|
*/
|
||||||
const shouldBreak =
|
const shouldBreak =
|
||||||
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item) ||
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemNoUnauthenticated(
|
||||||
AppBskyUnspeccedDefs.isThreadItemNotFound(item) ||
|
item.value,
|
||||||
AppBskyUnspeccedDefs.isThreadItemBlocked(item)
|
) ||
|
||||||
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemNotFound(item.value) ||
|
||||||
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemBlocked(item.value)
|
||||||
|
|
||||||
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 (AppBskyUnspeccedDefs.isThreadItemPost(item)) {
|
} else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) {
|
||||||
const lastSlice = slices[slices.length - 1]
|
const lastSlice = items[items.length - 1]
|
||||||
const isFirstReply =
|
const isFirstReply =
|
||||||
lastSlice.type === 'replyComposer' ||
|
lastSlice.type === 'replyComposer' ||
|
||||||
(lastSlice.type === 'threadSlice' && lastSlice.slice.depth === 0)
|
(lastSlice.type === 'threadPost' && lastSlice.depth === 0)
|
||||||
const parent = views.post({
|
const parent = views.threadPost({
|
||||||
item,
|
uri: item.uri,
|
||||||
|
depth: item.depth,
|
||||||
|
value: item.value,
|
||||||
oneUp: isFirstReply ? undefined : thread[i - 1],
|
oneUp: isFirstReply ? undefined : thread[i - 1],
|
||||||
oneDown: thread[i + 1],
|
oneDown: thread[i + 1],
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
@@ -177,7 +199,7 @@ export function sort(
|
|||||||
/*
|
/*
|
||||||
* Not hidden, so show it
|
* Not hidden, so show it
|
||||||
*/
|
*/
|
||||||
slices.push(parent)
|
items.push(parent)
|
||||||
} else {
|
} else {
|
||||||
const branch = getBranch(thread, i, item.depth)
|
const branch = getBranch(thread, i, item.depth)
|
||||||
const sortArray = parentMod.muted ? muted : hidden
|
const sortArray = parentMod.muted ? muted : hidden
|
||||||
@@ -191,9 +213,13 @@ export function sort(
|
|||||||
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 (AppBskyUnspeccedDefs.isThreadItemPost(child)) {
|
if (
|
||||||
const childPost = views.post({
|
AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(child.value)
|
||||||
item: child,
|
) {
|
||||||
|
const childPost = views.threadPost({
|
||||||
|
uri: child.uri,
|
||||||
|
depth: child.depth,
|
||||||
|
value: child.value,
|
||||||
oneUp: thread[ci - 1],
|
oneUp: thread[ci - 1],
|
||||||
oneDown: thread[ci + 1],
|
oneDown: thread[ci + 1],
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
@@ -231,7 +257,7 @@ export function sort(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slices,
|
items,
|
||||||
hidden,
|
hidden,
|
||||||
muted,
|
muted,
|
||||||
}
|
}
|
||||||
@@ -262,8 +288,6 @@ function getBranch(
|
|||||||
|
|
||||||
for (let ci = branchStartIndex + 1; ci < thread.length; ci++) {
|
for (let ci = branchStartIndex + 1; ci < thread.length; ci++) {
|
||||||
const next = thread[ci]
|
const next = thread[ci]
|
||||||
// ignore unknowns
|
|
||||||
if (!('depth' in next)) continue
|
|
||||||
if (next.depth > branchStartDepth) {
|
if (next.depth > branchStartDepth) {
|
||||||
end = ci
|
end = ci
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyUnspeccedDefs,
|
type AppBskyUnspeccedGetPostThreadV2,
|
||||||
type BskyThreadViewPreference,
|
type BskyThreadViewPreference,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -34,9 +34,11 @@ export enum HiddenReplyKind {
|
|||||||
|
|
||||||
export type Slice =
|
export type Slice =
|
||||||
| {
|
| {
|
||||||
type: 'threadSlice'
|
type: 'threadPost'
|
||||||
key: string
|
key: string
|
||||||
slice: Omit<AppBskyUnspeccedDefs.ThreadItemPost, 'post'> & {
|
uri: string
|
||||||
|
depth: number
|
||||||
|
value: Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost, 'post'> & {
|
||||||
post: Omit<AppBskyFeedDefs.PostView, 'record'> & {
|
post: Omit<AppBskyFeedDefs.PostView, 'record'> & {
|
||||||
record: AppBskyFeedPost.Record
|
record: AppBskyFeedPost.Record
|
||||||
}
|
}
|
||||||
@@ -49,19 +51,25 @@ export type Slice =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'threadSliceNoUnauthenticated'
|
type: 'threadPostNoUnauthenticated'
|
||||||
key: string
|
key: string
|
||||||
slice: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated
|
uri: string
|
||||||
|
depth: number
|
||||||
|
value: AppBskyUnspeccedGetPostThreadV2.ThreadItemNoUnauthenticated
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'threadSliceNotFound'
|
type: 'threadPostNotFound'
|
||||||
key: string
|
key: string
|
||||||
slice: AppBskyUnspeccedDefs.ThreadItemNotFound
|
uri: string
|
||||||
|
depth: number
|
||||||
|
value: AppBskyUnspeccedGetPostThreadV2.ThreadItemNotFound
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'threadSliceBlocked'
|
type: 'threadPostBlocked'
|
||||||
key: string
|
key: string
|
||||||
slice: AppBskyUnspeccedDefs.ThreadItemBlocked
|
uri: string
|
||||||
|
depth: number
|
||||||
|
value: AppBskyUnspeccedGetPostThreadV2.ThreadItemBlocked
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'replyComposer'
|
type: 'replyComposer'
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
type AppBskyEmbedRecord,
|
type AppBskyEmbedRecord,
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyUnspeccedDefs,
|
|
||||||
type AppBskyUnspeccedGetPostThreadV2,
|
type AppBskyUnspeccedGetPostThreadV2,
|
||||||
moderatePost,
|
moderatePost,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
@@ -12,102 +11,137 @@ import {
|
|||||||
import {type Slice} from '#/state/queries/usePostThread/types'
|
import {type Slice} from '#/state/queries/usePostThread/types'
|
||||||
import {embedViewRecordToPostView} from '#/state/queries/util'
|
import {embedViewRecordToPostView} from '#/state/queries/util'
|
||||||
|
|
||||||
export function noUnauthenticated({
|
export function threadPostNoUnauthenticated({
|
||||||
item,
|
uri,
|
||||||
}: {
|
depth,
|
||||||
item: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated
|
value,
|
||||||
}): Extract<Slice, {type: 'threadSliceNoUnauthenticated'}> {
|
}: AppBskyUnspeccedGetPostThreadV2.ThreadItem): Extract<
|
||||||
|
Slice,
|
||||||
|
{type: 'threadPostNoUnauthenticated'}
|
||||||
|
> {
|
||||||
return {
|
return {
|
||||||
type: 'threadSliceNoUnauthenticated',
|
type: 'threadPostNoUnauthenticated',
|
||||||
key: item.uri,
|
key: uri,
|
||||||
slice: item,
|
uri,
|
||||||
|
depth,
|
||||||
|
value: value as AppBskyUnspeccedGetPostThreadV2.ThreadItemNoUnauthenticated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function notFound({
|
export function threadPostNotFound({
|
||||||
item,
|
uri,
|
||||||
}: {
|
depth,
|
||||||
item: AppBskyUnspeccedDefs.ThreadItemNotFound
|
value,
|
||||||
}): Extract<Slice, {type: 'threadSliceNotFound'}> {
|
}: AppBskyUnspeccedGetPostThreadV2.ThreadItem): Extract<
|
||||||
|
Slice,
|
||||||
|
{type: 'threadPostNotFound'}
|
||||||
|
> {
|
||||||
return {
|
return {
|
||||||
type: 'threadSliceNotFound',
|
type: 'threadPostNotFound',
|
||||||
key: item.uri,
|
key: uri,
|
||||||
slice: item,
|
uri,
|
||||||
|
depth,
|
||||||
|
value: value as AppBskyUnspeccedGetPostThreadV2.ThreadItemNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function blocked({
|
export function threadPostBlocked({
|
||||||
item,
|
uri,
|
||||||
}: {
|
depth,
|
||||||
item: AppBskyUnspeccedDefs.ThreadItemBlocked
|
value,
|
||||||
}): Extract<Slice, {type: 'threadSliceBlocked'}> {
|
}: AppBskyUnspeccedGetPostThreadV2.ThreadItem): Extract<
|
||||||
|
Slice,
|
||||||
|
{type: 'threadPostBlocked'}
|
||||||
|
> {
|
||||||
return {
|
return {
|
||||||
type: 'threadSliceBlocked',
|
type: 'threadPostBlocked',
|
||||||
key: item.uri,
|
key: uri,
|
||||||
slice: item,
|
uri,
|
||||||
|
depth,
|
||||||
|
value: value as AppBskyUnspeccedGetPostThreadV2.ThreadItemBlocked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post({
|
export function threadPost({
|
||||||
item,
|
uri,
|
||||||
|
depth,
|
||||||
|
value,
|
||||||
oneUp,
|
oneUp,
|
||||||
oneDown,
|
oneDown,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
}: {
|
}: {
|
||||||
item: AppBskyUnspeccedDefs.ThreadItemPost
|
uri: string
|
||||||
|
depth: number
|
||||||
|
value: $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost>
|
||||||
oneUp?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
|
oneUp?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
|
||||||
oneDown?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
|
oneDown?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number]
|
||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
}): Extract<Slice, {type: 'threadSlice'}> {
|
}): Extract<Slice, {type: 'threadPost'}> {
|
||||||
return {
|
return {
|
||||||
type: 'threadSlice',
|
type: 'threadPost',
|
||||||
key: item.uri,
|
key: uri,
|
||||||
slice: {
|
uri,
|
||||||
...item,
|
depth,
|
||||||
|
value: {
|
||||||
|
...value,
|
||||||
post: {
|
post: {
|
||||||
...item.post,
|
...value.post,
|
||||||
record: item.post.record as AppBskyFeedPost.Record,
|
record: value.post.record as AppBskyFeedPost.Record,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
moderation: moderatePost(item.post, moderationOpts),
|
moderation: moderatePost(value.post, moderationOpts),
|
||||||
ui: {
|
ui: {
|
||||||
isAnchor: item.depth === 0,
|
isAnchor: depth === 0,
|
||||||
showParentReplyLine:
|
showParentReplyLine: !!oneUp && oneUp.depth < depth,
|
||||||
!!oneUp && 'depth' in oneUp && oneUp.depth < item.depth,
|
showChildReplyLine: !!oneDown && oneDown.depth > depth,
|
||||||
showChildReplyLine:
|
|
||||||
!!oneDown && 'depth' in oneDown && oneDown.depth > item.depth,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postViewToThreadPlaceholder(
|
export function postViewToThreadPlaceholder(
|
||||||
post: AppBskyFeedDefs.PostView,
|
post: AppBskyFeedDefs.PostView,
|
||||||
): $Typed<AppBskyUnspeccedDefs.ThreadItemPost> {
|
): $Typed<
|
||||||
|
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
|
||||||
|
value: $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost>
|
||||||
|
}
|
||||||
|
> {
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.unspecced.defs#threadItemPost',
|
$type: 'app.bsky.unspecced.getPostThreadV2#threadItem',
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
post,
|
|
||||||
depth: 0, // reset to 0 for highlighted post
|
depth: 0, // reset to 0 for highlighted post
|
||||||
|
value: {
|
||||||
|
$type: 'app.bsky.unspecced.getPostThreadV2#threadItemPost',
|
||||||
|
post,
|
||||||
isOPThread: false, // unknown
|
isOPThread: false, // unknown
|
||||||
hasOPLike: false, // unknown
|
hasOPLike: false, // unknown
|
||||||
|
// @ts-expect-error
|
||||||
hasUnhydratedReplies: false, // unknown
|
hasUnhydratedReplies: false, // unknown
|
||||||
// TODO test
|
// TODO test
|
||||||
hasUnhydratedParents: !!(post.record as AppBskyFeedPost.Record).reply, // unknown
|
hasUnhydratedParents: !!(post.record as AppBskyFeedPost.Record).reply, // unknown
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function embedViewToThreadPlaceholder(
|
export function embedViewToThreadPlaceholder(
|
||||||
record: AppBskyEmbedRecord.ViewRecord,
|
record: AppBskyEmbedRecord.ViewRecord,
|
||||||
): $Typed<AppBskyUnspeccedDefs.ThreadItemPost> {
|
): $Typed<
|
||||||
|
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
|
||||||
|
value: $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItemPost>
|
||||||
|
}
|
||||||
|
> {
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.unspecced.defs#threadItemPost',
|
$type: 'app.bsky.unspecced.getPostThreadV2#threadItem',
|
||||||
uri: record.uri,
|
uri: record.uri,
|
||||||
post: embedViewRecordToPostView(record),
|
|
||||||
depth: 0, // reset to 0 for highlighted post
|
depth: 0, // reset to 0 for highlighted post
|
||||||
|
value: {
|
||||||
|
$type: 'app.bsky.unspecced.getPostThreadV2#threadItemPost',
|
||||||
|
post: embedViewRecordToPostView(record),
|
||||||
isOPThread: false, // unknown
|
isOPThread: false, // unknown
|
||||||
hasOPLike: false, // unknown
|
hasOPLike: false, // unknown
|
||||||
|
// @ts-expect-error
|
||||||
hasUnhydratedReplies: false, // unknown
|
hasUnhydratedReplies: false, // unknown
|
||||||
// TODO test
|
// TODO test
|
||||||
hasUnhydratedParents: !!(record.value as AppBskyFeedPost.Record).reply, // unknown
|
hasUnhydratedParents: !!(record.value as AppBskyFeedPost.Record).reply, // unknown
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,10 @@ 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 AppBskyUnspeccedGetPostThreadV2,
|
||||||
type BskyAgent,
|
type BskyAgent,
|
||||||
type RichText,
|
type RichText,
|
||||||
AppBskyFeedGetPostThreadV2,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg, plural, Trans} from '@lingui/macro'
|
import {msg, plural, Trans} from '@lingui/macro'
|
||||||
@@ -57,8 +56,8 @@ import {useQueryClient} from '@tanstack/react-query'
|
|||||||
|
|
||||||
import * as apilib from '#/lib/api/index'
|
import * as apilib from '#/lib/api/index'
|
||||||
import {EmbeddingDisabledError} from '#/lib/api/resolve'
|
import {EmbeddingDisabledError} from '#/lib/api/resolve'
|
||||||
import {until} from '#/lib/async/until'
|
|
||||||
import {retry} from '#/lib/async/retry'
|
import {retry} from '#/lib/async/retry'
|
||||||
|
import {until} from '#/lib/async/until'
|
||||||
import {
|
import {
|
||||||
MAX_GRAPHEME_LENGTH,
|
MAX_GRAPHEME_LENGTH,
|
||||||
SUPPORTED_MIME_TYPES,
|
SUPPORTED_MIME_TYPES,
|
||||||
@@ -392,7 +391,7 @@ export const ComposePost = ({
|
|||||||
setIsPublishing(true)
|
setIsPublishing(true)
|
||||||
|
|
||||||
let postUri: string | undefined
|
let postUri: string | undefined
|
||||||
let posts: AppBskyFeedGetPostThreadV2.OutputSchema['thread'] = []
|
let posts: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'] = []
|
||||||
try {
|
try {
|
||||||
postUri = (
|
postUri = (
|
||||||
await apilib.post(agent, queryClient, {
|
await apilib.post(agent, queryClient, {
|
||||||
@@ -404,22 +403,27 @@ export const ComposePost = ({
|
|||||||
).uris[0]
|
).uris[0]
|
||||||
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.unspecced.getPostThreadV2({
|
const res = await agent.app.bsky.unspecced.getPostThreadV2({
|
||||||
uri: postUri!,
|
uri: postUri!,
|
||||||
above: 1,
|
above: 1,
|
||||||
below: thread.posts.length - 1,
|
below: thread.posts.length - 1,
|
||||||
branchingFactor: 1,
|
nestedBranchingFactor: 1,
|
||||||
})
|
})
|
||||||
const parent = res.data.thread.at(0)
|
const parent = res.data.thread.at(0)
|
||||||
if (!AppBskyUnspeccedDefs.isThreadItemPost(parent)) {
|
if (!parent) {
|
||||||
throw new Error(`Not ready`)
|
throw new Error(`Not ready`)
|
||||||
}
|
}
|
||||||
if (res.data.thread.length !== thread.posts.length + 1) {
|
if (res.data.thread.length !== thread.posts.length + 1) {
|
||||||
throw new Error(`Not ready`)
|
throw new Error(`Not ready`)
|
||||||
}
|
}
|
||||||
return res.data.thread
|
return res.data.thread
|
||||||
}, 1e3)
|
},
|
||||||
|
1e3,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await whenAppViewReady(agent, postUri, res => {
|
await whenAppViewReady(agent, postUri, res => {
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import {useCallback, useMemo, useRef, useState} from 'react'
|
import {useCallback, useMemo, useRef, useState} from 'react'
|
||||||
import {useWindowDimensions, View} from 'react-native'
|
import {useWindowDimensions, View} from 'react-native'
|
||||||
import {type AppBskyUnspeccedDefs} from '@atproto/api'
|
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
|
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
import {HITSLOP_10} from '#/lib/constants'
|
||||||
import {type CommonNavigatorParams, type NativeStackScreenProps} from '#/lib/routes/types'
|
import {
|
||||||
|
type CommonNavigatorParams,
|
||||||
|
type NativeStackScreenProps,
|
||||||
|
} from '#/lib/routes/types'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
@@ -157,11 +160,11 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
const layoutHeaderRef = useRef<View | null>(null)
|
const layoutHeaderRef = useRef<View | null>(null)
|
||||||
const anchorPostRef = useRef<View | null>(null)
|
const anchorPostRef = useRef<View | null>(null)
|
||||||
|
|
||||||
const optimisticOnPostReply =
|
const optimisticOnPostReply = (
|
||||||
({post}: {post: AppBskyUnspeccedDefs.ThreadItemPost}) =>
|
_: any,
|
||||||
(_: any, posts: AppBskyUnspeccedDefs.ThreadItemPost[]) => {
|
posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[],
|
||||||
|
) => {
|
||||||
if (posts.length) {
|
if (posts.length) {
|
||||||
console.log('insert', posts)
|
|
||||||
const parent = posts.at(0)
|
const parent = posts.at(0)
|
||||||
const replies = posts.slice(1)
|
const replies = posts.slice(1)
|
||||||
if (parent && replies.length) {
|
if (parent && replies.length) {
|
||||||
@@ -172,16 +175,16 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
|
|
||||||
const {openComposer} = useOpenComposer()
|
const {openComposer} = useOpenComposer()
|
||||||
const onReplyToAnchor = () => {
|
const onReplyToAnchor = () => {
|
||||||
const anchorPost = data?.slices.find(
|
const anchorPost = data?.items.find(
|
||||||
slice => slice.type === 'threadSlice' && slice.ui.isAnchor,
|
slice => slice.type === 'threadPost' && slice.ui.isAnchor,
|
||||||
)
|
)
|
||||||
if (anchorPost?.type !== 'threadSlice') {
|
if (anchorPost?.type !== 'threadPost') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const post = anchorPost.slice.post
|
const post = anchorPost.value.post
|
||||||
openComposer({
|
openComposer({
|
||||||
replyTo: {
|
replyTo: {
|
||||||
uri: anchorPost.slice.uri,
|
uri: anchorPost.uri,
|
||||||
cid: post.cid,
|
cid: post.cid,
|
||||||
text: post.record.text,
|
text: post.record.text,
|
||||||
author: post.author,
|
author: post.author,
|
||||||
@@ -189,42 +192,43 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
moderation: anchorPost.moderation,
|
moderation: anchorPost.moderation,
|
||||||
},
|
},
|
||||||
// @ts-expect-error TODO
|
// @ts-expect-error TODO
|
||||||
onPost: optimisticOnPostReply({post: anchorPost.slice}),
|
onPost: optimisticOnPostReply,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderItem = ({item, index}: {item: Slice; index: number}) => {
|
const renderItem = ({item, index}: {item: Slice; index: number}) => {
|
||||||
if (item.type === 'threadSlice') {
|
if (item.type === 'threadPost') {
|
||||||
return (
|
return (
|
||||||
<View ref={item.ui.isAnchor ? anchorPostRef : undefined}>
|
<View ref={item.ui.isAnchor ? anchorPostRef : undefined}>
|
||||||
<PostThreadItem
|
<PostThreadItem
|
||||||
post={item.slice.post}
|
post={item.value.post}
|
||||||
record={item.slice.post.record}
|
record={item.value.post.record}
|
||||||
threadgateRecord={data?.threadgate?.record ?? undefined}
|
threadgateRecord={data?.threadgate?.record ?? undefined}
|
||||||
moderation={item.moderation}
|
moderation={item.moderation}
|
||||||
treeView={treeViewEnabled}
|
treeView={treeViewEnabled}
|
||||||
depth={item.slice.depth}
|
depth={item.depth}
|
||||||
// TODO
|
// TODO
|
||||||
// prevPost={prev}
|
// prevPost={prev}
|
||||||
// nextPost={next}
|
// nextPost={next}
|
||||||
isHighlightedPost={item.ui.isAnchor}
|
isHighlightedPost={item.ui.isAnchor}
|
||||||
hasMore={item.slice.hasUnhydratedReplies}
|
// @ts-expect-error
|
||||||
|
hasMore={item.value.hasUnhydratedReplies}
|
||||||
showChildReplyLine={item.ui.showChildReplyLine}
|
showChildReplyLine={item.ui.showChildReplyLine}
|
||||||
showParentReplyLine={item.ui.showParentReplyLine}
|
showParentReplyLine={item.ui.showParentReplyLine}
|
||||||
hasPrecedingItem={
|
hasPrecedingItem={
|
||||||
item.ui.showParentReplyLine || !!item.slice.hasUnhydratedParents
|
// @ts-expect-error
|
||||||
|
item.ui.showParentReplyLine || !!item.value.hasUnhydratedParents
|
||||||
} // !!hasUnrevealedParents // TODO
|
} // !!hasUnrevealedParents // TODO
|
||||||
overrideBlur={
|
overrideBlur={
|
||||||
shownHiddenReplyKinds.has(HiddenReplyKind.Muted) &&
|
shownHiddenReplyKinds.has(HiddenReplyKind.Muted) && item.depth > 0
|
||||||
item.slice.depth > 0
|
|
||||||
}
|
}
|
||||||
// @ts-expect-error TODO
|
// @ts-expect-error TODO
|
||||||
onPostReply={optimisticOnPostReply({post: item.slice})}
|
onPostReply={optimisticOnPostReply}
|
||||||
hideTopBorder={index === 0} // && !item.slice.isParentLoading} // TODO
|
hideTopBorder={index === 0} // && !item.isParentLoading} // TODO
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else if (item.type === 'threadSliceBlocked') {
|
} else if (item.type === 'threadPostBlocked') {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -238,7 +242,7 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else if (item.type === 'threadSliceNotFound') {
|
} else if (item.type === 'threadPostNotFound') {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -332,7 +336,7 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
>
|
>
|
||||||
<List
|
<List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data={data?.slices ?? []}
|
data={data?.items ?? []}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
|
onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
|
||||||
|
|||||||
Reference in New Issue
Block a user