about summary refs log tree commit diff
path: root/src/state/queries/usePostThread/types.ts
blob: 5df7c2e42e1abe6ca7e22fbc55627038969000c4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import {
  type AppBskyFeedDefs,
  type AppBskyFeedPost,
  type AppBskyFeedThreadgate,
  type AppBskyUnspeccedDefs,
  type AppBskyUnspeccedGetPostThreadOtherV2,
  type AppBskyUnspeccedGetPostThreadV2,
  type ModerationDecision,
} from '@atproto/api'

export type ApiThreadItem =
  | AppBskyUnspeccedGetPostThreadV2.ThreadItem
  | AppBskyUnspeccedGetPostThreadOtherV2.ThreadItem

export const postThreadQueryKeyRoot = 'post-thread-v2' as const

export const createPostThreadQueryKey = (props: PostThreadParams) =>
  [postThreadQueryKeyRoot, props] as const

export const createPostThreadOtherQueryKey = (
  props: Omit<AppBskyUnspeccedGetPostThreadOtherV2.QueryParams, 'anchor'> & {
    anchor?: string
  },
) => [postThreadQueryKeyRoot, 'other', props] as const

export type PostThreadParams = Pick<
  AppBskyUnspeccedGetPostThreadV2.QueryParams,
  'sort' | 'prioritizeFollowedUsers'
> & {
  anchor?: string
  view: 'tree' | 'linear'
}

export type UsePostThreadQueryResult = {
  hasOtherReplies: boolean
  thread: AppBskyUnspeccedGetPostThreadV2.ThreadItem[]
  threadgate?: Omit<AppBskyFeedDefs.ThreadgateView, 'record'> & {
    record: AppBskyFeedThreadgate.Record
  }
}

export type ThreadItem =
  | {
      type: 'threadPost'
      key: string
      uri: string
      depth: number
      value: Omit<AppBskyUnspeccedDefs.ThreadItemPost, 'post'> & {
        post: Omit<AppBskyFeedDefs.PostView, 'record'> & {
          record: AppBskyFeedPost.Record
        }
      }
      isBlurred: boolean
      moderation: ModerationDecision
      ui: {
        isAnchor: boolean
        showParentReplyLine: boolean
        showChildReplyLine: boolean
        indent: number
        isLastChild: boolean
        skippedIndentIndices: Set<number>
        precedesChildReadMore: boolean
      }
    }
  | {
      type: 'threadPostNoUnauthenticated'
      key: string
      uri: string
      depth: number
      value: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated
      ui: {
        showParentReplyLine: boolean
        showChildReplyLine: boolean
      }
    }
  | {
      type: 'threadPostNotFound'
      key: string
      uri: string
      depth: number
      value: AppBskyUnspeccedDefs.ThreadItemNotFound
    }
  | {
      type: 'threadPostBlocked'
      key: string
      uri: string
      depth: number
      value: AppBskyUnspeccedDefs.ThreadItemBlocked
    }
  | {
      type: 'replyComposer'
      key: string
    }
  | {
      type: 'showOtherReplies'
      key: string
      onPress: () => void
    }
  | {
      /*
       * Read more replies, downwards in the thread.
       */
      type: 'readMore'
      key: string
      depth: number
      href: string
      moreReplies: number
      skippedIndentIndices: Set<number>
    }
  | {
      /*
       * Read more parents, upwards in the thread.
       */
      type: 'readMoreUp'
      key: string
      href: string
    }
  | {
      type: 'skeleton'
      key: string
      item: 'anchor' | 'reply' | 'replyComposer'
    }

/**
 * Metadata collected while traversing the raw data from the thread response.
 * Some values here can be computed immediately, while others need to be
 * computed during a second pass over the thread after we know things like
 * total number of replies, the reply index, etc.
 *
 * The idea here is that these values should be objectively true in all cases,
 * such that we can use them later — either individually on in composite — to
 * drive rendering behaviors.
 */
export type TraversalMetadata = {
  /**
   * The depth of the post in the reply tree, where 0 is the root post. This is
   * calculated on the server.
   */
  depth: number
  /**
   * Indicates if this item is a "read more" link preceding this post that
   * continues the thread upwards.
   */
  followsReadMoreUp: boolean
  /**
   * Indicates if the post is the last reply beneath its parent post.
   */
  isLastSibling: boolean
  /**
   * Indicates the post is the end-of-the-line for a given branch of replies.
   */
  isLastChild: boolean
  /**
   * Indicates if the post is the left-most AND lower-most branch of the reply
   * tree. Value corresponds to the depth at which this branch started.
   */
  isPartOfLastBranchFromDepth?: number
  /**
   * The depth of the slice immediately following this one, if it exists.
   */
  nextItemDepth?: number
  /**
   * This is a live reference to the parent metadata object. Mutations to this
   * are available for later use in children.
   */
  parentMetadata?: TraversalMetadata
  /**
   * Populated during the final traversal of the thread. Denotes whether
   * there is a "Read more" link for this item immediately following
   * this item.
   */
  precedesChildReadMore: boolean
  /**
   * The depth of the slice immediately preceding this one, if it exists.
   */
  prevItemDepth?: number
  /**
   * Any data needed to be passed along to the "read more" items. Keep this
   * trim for better memory usage.
   */
  postData: {
    uri: string
    authorHandle: string
  }
  /**
   * The total number of replies to this post, including those not hydrated
   * and returned by the response.
   */
  repliesCount: number
  /**
   * The number of replies to this post not hydrated and returned by the
   * response.
   */
  repliesUnhydrated: number
  /**
   * The number of replies that have been seen so far in the traversal.
   * Excludes replies that are moderated in some way, since those are not
   * "seen" on first load. Use `repliesIndexCounter` for the total number of
   * replies that were hydrated in the response.
   *
   * After traversal, we can use this to calculate if we actually got all the
   * replies we expected, or if some were blocked, etc.
   */
  repliesSeenCounter: number
  /**
   * The total number of replies to this post hydrated in this response. Used
   * for populating the `replyIndex` of the post by referencing this value on
   * the parent.
   */
  repliesIndexCounter: number
  /**
   * The index-0-based index of this reply in the parent post's replies.
   */
  replyIndex: number
  /**
   * Each slice is responsible for rendering reply lines based on its depth.
   * This value corresponds to any line indices that can be skipped e.g.
   * because there are no further replies below this sub-tree to render.
   */
  skippedIndentIndices: Set<number>
  /**
   * Indicates and stores parent data IF that parent has additional unhydrated
   * replies. This value is passed down to children along the left/lower-most
   * branch of the tree. When the end is reached, a "read more" is inserted.
   */
  upcomingParentReadMore?: TraversalMetadata
}