about summary refs log tree commit diff
path: root/src/state/models/content/post-thread-item.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/content/post-thread-item.ts')
-rw-r--r--src/state/models/content/post-thread-item.ts141
1 files changed, 141 insertions, 0 deletions
diff --git a/src/state/models/content/post-thread-item.ts b/src/state/models/content/post-thread-item.ts
new file mode 100644
index 000000000..c33415507
--- /dev/null
+++ b/src/state/models/content/post-thread-item.ts
@@ -0,0 +1,141 @@
+import {makeAutoObservable} from 'mobx'
+import {
+  AppBskyFeedPost as FeedPost,
+  AppBskyFeedDefs,
+  RichText,
+} from '@atproto/api'
+import {RootStoreModel} from '../root-store'
+import {PostLabelInfo, PostModeration} from 'lib/labeling/types'
+import {PostsFeedItemModel} from '../feeds/post'
+
+type PostView = AppBskyFeedDefs.PostView
+
+// NOTE: this model uses the same data as PostsFeedItemModel, but is used for
+// rendering a single post in a thread view, and has additional state
+// for rendering the thread view, but calls the same data methods
+// as PostsFeedItemModel
+// TODO: refactor as an extension or subclass of PostsFeedItemModel
+export class PostThreadItemModel {
+  // ui state
+  _reactKey: string = ''
+  _depth = 0
+  _isHighlightedPost = false
+  _showParentReplyLine = false
+  _showChildReplyLine = false
+  _hasMore = false
+
+  // data
+  data: PostsFeedItemModel
+  post: PostView
+  postRecord?: FeedPost.Record
+  richText?: RichText
+  parent?:
+    | PostThreadItemModel
+    | AppBskyFeedDefs.NotFoundPost
+    | AppBskyFeedDefs.BlockedPost
+  replies?: (PostThreadItemModel | AppBskyFeedDefs.NotFoundPost)[]
+
+  constructor(
+    public rootStore: RootStoreModel,
+    v: AppBskyFeedDefs.ThreadViewPost,
+  ) {
+    this._reactKey = `thread-${v.post.uri}`
+    this.data = new PostsFeedItemModel(rootStore, this._reactKey, v)
+    this.post = this.data.post
+    this.postRecord = this.data.postRecord
+    this.richText = this.data.richText
+    // replies and parent are handled via assignTreeModels
+    makeAutoObservable(this, {rootStore: false})
+  }
+
+  get uri() {
+    return this.post.uri
+  }
+  get parentUri() {
+    return this.postRecord?.reply?.parent.uri
+  }
+
+  get rootUri(): string {
+    if (this.postRecord?.reply?.root.uri) {
+      return this.postRecord.reply.root.uri
+    }
+    return this.post.uri
+  }
+  get isThreadMuted() {
+    return this.rootStore.mutedThreads.uris.has(this.rootUri)
+  }
+
+  get labelInfo(): PostLabelInfo {
+    return this.data.labelInfo
+  }
+
+  get moderation(): PostModeration {
+    return this.data.moderation
+  }
+
+  assignTreeModels(
+    v: AppBskyFeedDefs.ThreadViewPost,
+    highlightedPostUri: string,
+    includeParent = true,
+    includeChildren = true,
+  ) {
+    // parents
+    if (includeParent && v.parent) {
+      if (AppBskyFeedDefs.isThreadViewPost(v.parent)) {
+        const parentModel = new PostThreadItemModel(this.rootStore, v.parent)
+        parentModel._depth = this._depth - 1
+        parentModel._showChildReplyLine = true
+        if (v.parent.parent) {
+          parentModel._showParentReplyLine = true
+          parentModel.assignTreeModels(
+            v.parent,
+            highlightedPostUri,
+            true,
+            false,
+          )
+        }
+        this.parent = parentModel
+      } else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) {
+        this.parent = v.parent
+      } else if (AppBskyFeedDefs.isBlockedPost(v.parent)) {
+        this.parent = v.parent
+      }
+    }
+    // replies
+    if (includeChildren && v.replies) {
+      const replies = []
+      for (const item of v.replies) {
+        if (AppBskyFeedDefs.isThreadViewPost(item)) {
+          const itemModel = new PostThreadItemModel(this.rootStore, item)
+          itemModel._depth = this._depth + 1
+          itemModel._showParentReplyLine =
+            itemModel.parentUri !== highlightedPostUri && replies.length === 0
+          if (item.replies?.length) {
+            itemModel._showChildReplyLine = true
+            itemModel.assignTreeModels(item, highlightedPostUri, false, true)
+          }
+          replies.push(itemModel)
+        } else if (AppBskyFeedDefs.isNotFoundPost(item)) {
+          replies.push(item)
+        }
+      }
+      this.replies = replies
+    }
+  }
+
+  async toggleLike() {
+    this.data.toggleLike()
+  }
+
+  async toggleRepost() {
+    this.data.toggleRepost()
+  }
+
+  async toggleThreadMute() {
+    this.data.toggleThreadMute()
+  }
+
+  async delete() {
+    this.data.delete()
+  }
+}