about summary refs log tree commit diff
path: root/src/state/models/feed-view.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/feed-view.ts')
-rw-r--r--src/state/models/feed-view.ts279
1 files changed, 113 insertions, 166 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts
index 3c4e68e2d..ca00d5e2b 100644
--- a/src/state/models/feed-view.ts
+++ b/src/state/models/feed-view.ts
@@ -1,37 +1,28 @@
 import {makeAutoObservable, runInAction} from 'mobx'
-import {Record as PostRecord} from '../../third-party/api/src/client/types/app/bsky/feed/post'
 import * as GetTimeline from '../../third-party/api/src/client/types/app/bsky/feed/getTimeline'
-import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
+import {
+  Main as FeedViewPost,
+  ReasonTrend,
+  ReasonRepost,
+} from '../../third-party/api/src/client/types/app/bsky/feed/feedViewPost'
+import {View as PostView} from '../../third-party/api/src/client/types/app/bsky/feed/post'
 import * as GetAuthorFeed from '../../third-party/api/src/client/types/app/bsky/feed/getAuthorFeed'
-import {PostThreadViewModel} from './post-thread-view'
 import {AtUri} from '../../third-party/uri'
 import {RootStoreModel} from './root-store'
 import * as apilib from '../lib/api'
 import {cleanError} from '../../lib/strings'
-import {isObj, hasProp} from '../lib/type-guards'
 
 const PAGE_SIZE = 30
 
 let _idCounter = 0
 
-type FeedItem = GetTimeline.FeedItem | GetAuthorFeed.FeedItem
-type FeedItemWithThreadMeta = FeedItem & {
+type FeedViewPostWithThreadMeta = FeedViewPost & {
   _isThreadParent?: boolean
   _isThreadChildElided?: boolean
   _isThreadChild?: boolean
 }
 
-export class FeedItemMyStateModel {
-  repost?: string
-  upvote?: string
-  downvote?: string
-
-  constructor() {
-    makeAutoObservable(this)
-  }
-}
-
-export class FeedItemModel implements GetTimeline.FeedItem {
+export class FeedItemModel {
   // ui state
   _reactKey: string = ''
   _isThreadParent: boolean = false
@@ -39,153 +30,128 @@ export class FeedItemModel implements GetTimeline.FeedItem {
   _isThreadChild: boolean = false
 
   // data
-  uri: string = ''
-  cid: string = ''
-  author: ActorRef.WithInfo = {
-    did: '',
-    handle: '',
-    displayName: '',
-    declaration: {cid: '', actorType: ''},
-    avatar: undefined,
-  }
-  repostedBy?: ActorRef.WithInfo
-  trendedBy?: ActorRef.WithInfo
-  record: Record<string, unknown> = {}
-  embed?: GetTimeline.FeedItem['embed']
-  replyCount: number = 0
-  repostCount: number = 0
-  upvoteCount: number = 0
-  downvoteCount: number = 0
-  indexedAt: string = ''
-  myState = new FeedItemMyStateModel()
-
-  // additional data
-  additionalParentPost?: PostThreadViewModel
+  post: PostView
+  reply?: FeedViewPost['reply']
+  replyParent?: FeedItemModel
+  reason?: FeedViewPost['reason']
 
   constructor(
     public rootStore: RootStoreModel,
     reactKey: string,
-    v: FeedItemWithThreadMeta,
+    v: FeedViewPostWithThreadMeta,
   ) {
-    makeAutoObservable(this, {rootStore: false})
     this._reactKey = reactKey
-    this.copy(v)
+    this.post = v.post
+    this.reply = v.reply
+    if (v.reply?.parent) {
+      this.replyParent = new FeedItemModel(rootStore, '', {
+        post: v.reply.parent,
+      })
+    }
+    this.reason = v.reason
     this._isThreadParent = v._isThreadParent || false
     this._isThreadChild = v._isThreadChild || false
     this._isThreadChildElided = v._isThreadChildElided || false
+    makeAutoObservable(this, {rootStore: false})
+  }
+
+  copy(v: FeedViewPost) {
+    this.post = v.post
+    this.reply = v.reply
+    if (v.reply?.parent) {
+      this.replyParent = new FeedItemModel(this.rootStore, '', {
+        post: v.reply.parent,
+      })
+    } else {
+      this.replyParent = undefined
+    }
+    this.reason = v.reason
   }
 
-  copy(v: GetTimeline.FeedItem | GetAuthorFeed.FeedItem) {
-    this.uri = v.uri
-    this.cid = v.cid
-    this.author = v.author
-    this.repostedBy = v.repostedBy
-    this.trendedBy = v.trendedBy
-    this.record = v.record
-    this.embed = v.embed
-    this.replyCount = v.replyCount
-    this.repostCount = v.repostCount
-    this.upvoteCount = v.upvoteCount
-    this.downvoteCount = v.downvoteCount
-    this.indexedAt = v.indexedAt
-    if (v.myState) {
-      this.myState.upvote = v.myState.upvote
-      this.myState.downvote = v.myState.downvote
-      this.myState.repost = v.myState.repost
+  get reasonRepost(): ReasonRepost | undefined {
+    if (this.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost') {
+      return this.reason as ReasonRepost
+    }
+  }
+
+  get reasonTrend(): ReasonTrend | undefined {
+    if (this.reason?.$type === 'app.bsky.feed.feedViewPost#reasonTrend') {
+      return this.reason as ReasonTrend
     }
   }
 
   async toggleUpvote() {
-    const wasUpvoted = !!this.myState.upvote
-    const wasDownvoted = !!this.myState.downvote
+    const wasUpvoted = !!this.post.viewer.upvote
+    const wasDownvoted = !!this.post.viewer.downvote
     const res = await this.rootStore.api.app.bsky.feed.setVote({
       subject: {
-        uri: this.uri,
-        cid: this.cid,
+        uri: this.post.uri,
+        cid: this.post.cid,
       },
       direction: wasUpvoted ? 'none' : 'up',
     })
     runInAction(() => {
       if (wasDownvoted) {
-        this.downvoteCount--
+        this.post.downvoteCount--
       }
       if (wasUpvoted) {
-        this.upvoteCount--
+        this.post.upvoteCount--
       } else {
-        this.upvoteCount++
+        this.post.upvoteCount++
       }
-      this.myState.upvote = res.data.upvote
-      this.myState.downvote = res.data.downvote
+      this.post.viewer.upvote = res.data.upvote
+      this.post.viewer.downvote = res.data.downvote
     })
   }
 
   async toggleDownvote() {
-    const wasUpvoted = !!this.myState.upvote
-    const wasDownvoted = !!this.myState.downvote
+    const wasUpvoted = !!this.post.viewer.upvote
+    const wasDownvoted = !!this.post.viewer.downvote
     const res = await this.rootStore.api.app.bsky.feed.setVote({
       subject: {
-        uri: this.uri,
-        cid: this.cid,
+        uri: this.post.uri,
+        cid: this.post.cid,
       },
       direction: wasDownvoted ? 'none' : 'down',
     })
     runInAction(() => {
       if (wasUpvoted) {
-        this.upvoteCount--
+        this.post.upvoteCount--
       }
       if (wasDownvoted) {
-        this.downvoteCount--
+        this.post.downvoteCount--
       } else {
-        this.downvoteCount++
+        this.post.downvoteCount++
       }
-      this.myState.upvote = res.data.upvote
-      this.myState.downvote = res.data.downvote
+      this.post.viewer.upvote = res.data.upvote
+      this.post.viewer.downvote = res.data.downvote
     })
   }
 
   async toggleRepost() {
-    if (this.myState.repost) {
-      await apilib.unrepost(this.rootStore, this.myState.repost)
+    if (this.post.viewer.repost) {
+      await apilib.unrepost(this.rootStore, this.post.viewer.repost)
       runInAction(() => {
-        this.repostCount--
-        this.myState.repost = undefined
+        this.post.repostCount--
+        this.post.viewer.repost = undefined
       })
     } else {
-      const res = await apilib.repost(this.rootStore, this.uri, this.cid)
+      const res = await apilib.repost(
+        this.rootStore,
+        this.post.uri,
+        this.post.cid,
+      )
       runInAction(() => {
-        this.repostCount++
-        this.myState.repost = res.uri
+        this.post.repostCount++
+        this.post.viewer.repost = res.uri
       })
     }
   }
 
   async delete() {
     await this.rootStore.api.app.bsky.feed.post.delete({
-      did: this.author.did,
-      rkey: new AtUri(this.uri).rkey,
-    })
-  }
-
-  get needsAdditionalData() {
-    if (
-      (this.record as PostRecord).reply?.parent?.uri &&
-      !this._isThreadChild
-    ) {
-      return !this.additionalParentPost
-    }
-    return false
-  }
-
-  async fetchAdditionalData() {
-    if (!this.needsAdditionalData) {
-      return
-    }
-    this.additionalParentPost = new PostThreadViewModel(this.rootStore, {
-      uri: (this.record as PostRecord).reply?.parent.uri,
-      depth: 0,
-    })
-    await this.additionalParentPost.setup().catch(e => {
-      console.error('Failed to load post needed by notification', e)
+      did: this.post.author.did,
+      rkey: new AtUri(this.post.uri).rkey,
     })
   }
 }
@@ -244,12 +210,11 @@ export class FeedModel {
 
   get nonReplyFeed() {
     return this.feed.filter(
-      post =>
-        !post.record.reply || // not a reply
-        !!post.repostedBy || // or a repost
-        !!post.trendedBy || // or a trend
-        post._isThreadParent || // but allow if it's a thread by the user
-        post._isThreadChild,
+      item =>
+        !item.reply || // not a reply
+        ((item._isThreadParent || // but allow if it's a thread by the user
+          item._isThreadChild) &&
+          item.reply?.root.author.did === item.post.author.did),
     )
   }
 
@@ -335,7 +300,7 @@ export class FeedModel {
     const res = await this._getFeed({limit: 1})
     const currentLatestUri = this.pollCursor
     const receivedLatestUri = res.data.feed[0]
-      ? res.data.feed[0].uri
+      ? res.data.feed[0].post.uri
       : undefined
     const hasNewLatest = Boolean(
       receivedLatestUri &&
@@ -435,7 +400,9 @@ export class FeedModel {
         }
         this._updateAll(res)
         numToFetch -= res.data.feed.length
-        cursor = this.feed[res.data.feed.length - 1].indexedAt
+        cursor = this.feed[res.data.feed.length - 1]
+          ? ts(this.feed[res.data.feed.length - 1])
+          : undefined
         console.log(numToFetch, cursor, res.data.feed.length)
       } while (numToFetch > 0)
       this._xIdle()
@@ -447,7 +414,7 @@ export class FeedModel {
   private async _replaceAll(
     res: GetTimeline.Response | GetAuthorFeed.Response,
   ) {
-    this.pollCursor = res.data.feed[0]?.uri
+    this.pollCursor = res.data.feed[0]?.post.uri
     return this._appendAll(res, true)
   }
 
@@ -460,7 +427,6 @@ export class FeedModel {
 
     const reorgedFeed = preprocessFeed(res.data.feed)
 
-    const promises = []
     const toAppend: FeedItemModel[] = []
     for (const item of reorgedFeed) {
       const itemModel = new FeedItemModel(
@@ -468,16 +434,8 @@ export class FeedModel {
         `item-${_idCounter++}`,
         item,
       )
-      if (itemModel.needsAdditionalData) {
-        promises.push(
-          itemModel.fetchAdditionalData().catch(e => {
-            console.error('Failure during feed-view _appendAll()', e)
-          }),
-        )
-      }
       toAppend.push(itemModel)
     }
-    await Promise.all(promises)
     runInAction(() => {
       if (replace) {
         this.feed = toAppend
@@ -490,12 +448,11 @@ export class FeedModel {
   private async _prependAll(
     res: GetTimeline.Response | GetAuthorFeed.Response,
   ) {
-    this.pollCursor = res.data.feed[0]?.uri
+    this.pollCursor = res.data.feed[0]?.post.uri
 
-    const promises = []
     const toPrepend: FeedItemModel[] = []
     for (const item of res.data.feed) {
-      if (this.feed.find(item2 => item2.uri === item.uri)) {
+      if (this.feed.find(item2 => item2.post.uri === item.post.uri)) {
         break // stop here - we've hit a post we already have
       }
 
@@ -504,16 +461,8 @@ export class FeedModel {
         `item-${_idCounter++}`,
         item,
       )
-      if (itemModel.needsAdditionalData) {
-        promises.push(
-          itemModel.fetchAdditionalData().catch(e => {
-            console.error('Failure during feed-view _prependAll()', e)
-          }),
-        )
-      }
       toPrepend.push(itemModel)
     }
-    await Promise.all(promises)
     runInAction(() => {
       this.feed = toPrepend.concat(this.feed)
     })
@@ -524,9 +473,10 @@ export class FeedModel {
       const existingItem = this.feed.find(
         // HACK: need to find the reposts and trends item, so we have to check for that -prf
         item2 =>
-          item.uri === item2.uri &&
-          item.repostedBy?.did === item2.repostedBy?.did &&
-          item.trendedBy?.did === item2.trendedBy?.did,
+          item.uri === item2.post.uri &&
+          item.reason?.$trend === item2.reason?.$trend &&
+          // @ts-ignore todo
+          item.reason?.by?.did === item2.reason?.by?.did,
       )
       if (existingItem) {
         existingItem.copy(item)
@@ -554,17 +504,19 @@ interface Slice {
   index: number
   length: number
 }
-function preprocessFeed(feed: FeedItem[]): FeedItemWithThreadMeta[] {
-  const reorg: FeedItemWithThreadMeta[] = []
+function preprocessFeed(feed: FeedViewPost[]): FeedViewPostWithThreadMeta[] {
+  const reorg: FeedViewPostWithThreadMeta[] = []
 
   // phase one: identify threads and reorganize them into the feed so
   // that they are in order and marked as part of a thread
   for (let i = feed.length - 1; i >= 0; i--) {
-    const item = feed[i] as FeedItemWithThreadMeta
+    const item = feed[i] as FeedViewPostWithThreadMeta
 
     const selfReplyUri = getSelfReplyUri(item)
     if (selfReplyUri) {
-      const parentIndex = reorg.findIndex(item2 => item2.uri === selfReplyUri)
+      const parentIndex = reorg.findIndex(
+        item2 => item2.post.uri === selfReplyUri,
+      )
       if (parentIndex !== -1 && !reorg[parentIndex]._isThreadParent) {
         reorg[parentIndex]._isThreadParent = true
         item._isThreadChild = true
@@ -579,7 +531,7 @@ function preprocessFeed(feed: FeedItem[]): FeedItemWithThreadMeta[] {
   let activeSlice = -1
   let threadSlices: Slice[] = []
   for (let i = 0; i < reorg.length; i++) {
-    const item = reorg[i] as FeedItemWithThreadMeta
+    const item = reorg[i] as FeedViewPostWithThreadMeta
     if (activeSlice === -1) {
       if (item._isThreadParent) {
         activeSlice = i
@@ -602,14 +554,12 @@ function preprocessFeed(feed: FeedItem[]): FeedItemWithThreadMeta[] {
   // phase three: reorder the feed so that the timestamp of the
   // last post in a thread establishes its ordering
   for (const slice of threadSlices) {
-    const removed: FeedItemWithThreadMeta[] = reorg.splice(
+    const removed: FeedViewPostWithThreadMeta[] = reorg.splice(
       slice.index,
       slice.length,
     )
-    const targetDate = new Date(removed[removed.length - 1].indexedAt)
-    let newIndex = reorg.findIndex(
-      item => new Date(item.indexedAt) < targetDate,
-    )
+    const targetDate = new Date(ts(removed[removed.length - 1]))
+    let newIndex = reorg.findIndex(item => new Date(ts(item)) < targetDate)
     if (newIndex === -1) {
       newIndex = reorg.length
     }
@@ -630,20 +580,17 @@ function preprocessFeed(feed: FeedItem[]): FeedItemWithThreadMeta[] {
   return reorg
 }
 
-function getSelfReplyUri(
-  item: GetTimeline.FeedItem | GetAuthorFeed.FeedItem,
-): string | undefined {
-  if (
-    isObj(item.record) &&
-    hasProp(item.record, 'reply') &&
-    isObj(item.record.reply) &&
-    hasProp(item.record.reply, 'parent') &&
-    isObj(item.record.reply.parent) &&
-    hasProp(item.record.reply.parent, 'uri') &&
-    typeof item.record.reply.parent.uri === 'string'
-  ) {
-    if (new AtUri(item.record.reply.parent.uri).host === item.author.did) {
-      return item.record.reply.parent.uri
-    }
+function getSelfReplyUri(item: FeedViewPost): string | undefined {
+  return item.reply?.parent.author.did === item.post.author.did
+    ? item.reply?.parent.uri
+    : undefined
+}
+
+function ts(item: FeedViewPost | FeedItemModel): string {
+  if (item.reason?.indexedAt) {
+    // @ts-ignore need better type checks
+    return item.reason.indexedAt
   }
+  console.log(item)
+  return item.post.indexedAt
 }