about summary refs log tree commit diff
path: root/src/state/models/content
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/content')
-rw-r--r--src/state/models/content/list.ts2
-rw-r--r--src/state/models/content/post-thread-item.ts8
-rw-r--r--src/state/models/content/post-thread.ts101
-rw-r--r--src/state/models/content/profile.ts58
4 files changed, 124 insertions, 45 deletions
diff --git a/src/state/models/content/list.ts b/src/state/models/content/list.ts
index 2498cf581..5d4ffb4fa 100644
--- a/src/state/models/content/list.ts
+++ b/src/state/models/content/list.ts
@@ -306,7 +306,7 @@ export class ListModel {
     this.hasMore = !!this.loadMoreCursor
     this.list = res.data.list
     this.items = this.items.concat(
-      res.data.items.map(item => ({...item, _reactKey: item.subject})),
+      res.data.items.map(item => ({...item, _reactKey: item.subject.did})),
     )
   }
 }
diff --git a/src/state/models/content/post-thread-item.ts b/src/state/models/content/post-thread-item.ts
index 14aa607ed..942f3acc8 100644
--- a/src/state/models/content/post-thread-item.ts
+++ b/src/state/models/content/post-thread-item.ts
@@ -3,9 +3,9 @@ import {
   AppBskyFeedPost as FeedPost,
   AppBskyFeedDefs,
   RichText,
+  PostModeration,
 } from '@atproto/api'
 import {RootStoreModel} from '../root-store'
-import {PostLabelInfo, PostModeration} from 'lib/labeling/types'
 import {PostsFeedItemModel} from '../feeds/post'
 
 type PostView = AppBskyFeedDefs.PostView
@@ -67,10 +67,6 @@ export class PostThreadItemModel {
     return this.data.isThreadMuted
   }
 
-  get labelInfo(): PostLabelInfo {
-    return this.data.labelInfo
-  }
-
   get moderation(): PostModeration {
     return this.data.moderation
   }
@@ -111,7 +107,7 @@ export class PostThreadItemModel {
           const itemModel = new PostThreadItemModel(this.rootStore, item)
           itemModel._depth = this._depth + 1
           itemModel._showParentReplyLine =
-            itemModel.parentUri !== highlightedPostUri && replies.length === 0
+            itemModel.parentUri !== highlightedPostUri
           if (item.replies?.length) {
             itemModel._showChildReplyLine = true
             itemModel.assignTreeModels(item, highlightedPostUri, false, true)
diff --git a/src/state/models/content/post-thread.ts b/src/state/models/content/post-thread.ts
index 0a67c783e..85ed13cb4 100644
--- a/src/state/models/content/post-thread.ts
+++ b/src/state/models/content/post-thread.ts
@@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
 import {
   AppBskyFeedGetPostThread as GetPostThread,
   AppBskyFeedDefs,
+  PostModeration,
 } from '@atproto/api'
 import {AtUri} from '@atproto/api'
 import {RootStoreModel} from '../root-store'
@@ -12,6 +13,8 @@ import {PostThreadItemModel} from './post-thread-item'
 export class PostThreadModel {
   // state
   isLoading = false
+  isLoadingFromCache = false
+  isFromCache = false
   isRefreshing = false
   hasLoaded = false
   error = ''
@@ -20,7 +23,7 @@ export class PostThreadModel {
   params: GetPostThread.QueryParams
 
   // data
-  thread?: PostThreadItemModel
+  thread?: PostThreadItemModel | null = null
   isBlocked = false
 
   constructor(
@@ -52,7 +55,7 @@ export class PostThreadModel {
   }
 
   get hasContent() {
-    return typeof this.thread !== 'undefined'
+    return !!this.thread
   }
 
   get hasError() {
@@ -82,10 +85,16 @@ export class PostThreadModel {
     if (!this.resolvedUri) {
       await this._resolveUri()
     }
+
     if (this.hasContent) {
       await this.update()
     } else {
-      await this._load()
+      const precache = this.rootStore.posts.cache.get(this.resolvedUri)
+      if (precache) {
+        await this._loadPrecached(precache)
+      } else {
+        await this._load()
+      }
     }
   }
 
@@ -169,6 +178,37 @@ export class PostThreadModel {
     })
   }
 
+  async _loadPrecached(precache: AppBskyFeedDefs.PostView) {
+    // start with the cached version
+    this.isLoadingFromCache = true
+    this.isFromCache = true
+    this._replaceAll({
+      success: true,
+      headers: {},
+      data: {
+        thread: {
+          post: precache,
+        },
+      },
+    })
+    this._xIdle()
+
+    // then update in the background
+    try {
+      const res = await this.rootStore.agent.getPostThread(
+        Object.assign({}, this.params, {uri: this.resolvedUri}),
+      )
+      this._replaceAll(res)
+    } catch (e: any) {
+      console.log(e)
+      this._xIdle(e)
+    } finally {
+      runInAction(() => {
+        this.isLoadingFromCache = false
+      })
+    }
+  }
+
   async _load(isRefreshing = false) {
     if (this.hasLoaded && !isRefreshing) {
       return
@@ -192,7 +232,6 @@ export class PostThreadModel {
       return
     }
     pruneReplies(res.data.thread)
-    sortThread(res.data.thread)
     const thread = new PostThreadItemModel(
       this.rootStore,
       res.data.thread as AppBskyFeedDefs.ThreadViewPost,
@@ -202,6 +241,7 @@ export class PostThreadModel {
       res.data.thread as AppBskyFeedDefs.ThreadViewPost,
       thread.uri,
     )
+    sortThread(thread)
     this.thread = thread
   }
 }
@@ -223,24 +263,28 @@ function pruneReplies(post: MaybePost) {
   }
 }
 
-function sortThread(post: MaybePost) {
-  if (post.notFound) {
+type MaybeThreadItem =
+  | PostThreadItemModel
+  | AppBskyFeedDefs.NotFoundPost
+  | AppBskyFeedDefs.BlockedPost
+function sortThread(item: MaybeThreadItem) {
+  if ('notFound' in item) {
     return
   }
-  post = post as AppBskyFeedDefs.ThreadViewPost
-  if (post.replies) {
-    post.replies.sort((a: MaybePost, b: MaybePost) => {
-      post = post as AppBskyFeedDefs.ThreadViewPost
-      if (a.notFound) {
+  item = item as PostThreadItemModel
+  if (item.replies) {
+    item.replies.sort((a: MaybeThreadItem, b: MaybeThreadItem) => {
+      if ('notFound' in a && a.notFound) {
         return 1
       }
-      if (b.notFound) {
+      if ('notFound' in b && b.notFound) {
         return -1
       }
-      a = a as AppBskyFeedDefs.ThreadViewPost
-      b = b as AppBskyFeedDefs.ThreadViewPost
-      const aIsByOp = a.post.author.did === post.post.author.did
-      const bIsByOp = b.post.author.did === post.post.author.did
+      item = item as PostThreadItemModel
+      a = a as PostThreadItemModel
+      b = b as PostThreadItemModel
+      const aIsByOp = a.post.author.did === item.post.author.did
+      const bIsByOp = b.post.author.did === item.post.author.did
       if (aIsByOp && bIsByOp) {
         return a.post.indexedAt.localeCompare(b.post.indexedAt) // oldest
       } else if (aIsByOp) {
@@ -248,8 +292,31 @@ function sortThread(post: MaybePost) {
       } else if (bIsByOp) {
         return 1 // op's own reply
       }
+      // put moderated content down at the bottom
+      if (modScore(a.moderation) !== modScore(b.moderation)) {
+        return modScore(a.moderation) - modScore(b.moderation)
+      }
       return b.post.indexedAt.localeCompare(a.post.indexedAt) // newest
     })
-    post.replies.forEach(reply => sortThread(reply))
+    item.replies.forEach(reply => sortThread(reply))
+  }
+}
+
+function modScore(mod: PostModeration): number {
+  if (mod.content.blur && mod.content.noOverride) {
+    return 5
+  }
+  if (mod.content.blur) {
+    return 4
+  }
+  if (mod.content.alert) {
+    return 3
+  }
+  if (mod.embed.blur && mod.embed.noOverride) {
+    return 2
+  }
+  if (mod.embed.blur) {
+    return 1
   }
+  return 0
 }
diff --git a/src/state/models/content/profile.ts b/src/state/models/content/profile.ts
index 34b2ea28e..26fa6008c 100644
--- a/src/state/models/content/profile.ts
+++ b/src/state/models/content/profile.ts
@@ -6,18 +6,14 @@ import {
   AppBskyActorGetProfile as GetProfile,
   AppBskyActorProfile,
   RichText,
+  moderateProfile,
+  ProfileModeration,
 } from '@atproto/api'
 import {RootStoreModel} from '../root-store'
 import * as apilib from 'lib/api/index'
 import {cleanError} from 'lib/strings/errors'
 import {FollowState} from '../cache/my-follows'
 import {Image as RNImage} from 'react-native-image-crop-picker'
-import {ProfileLabelInfo, ProfileModeration} from 'lib/labeling/types'
-import {
-  getProfileModeration,
-  filterAccountLabels,
-  filterProfileLabels,
-} from 'lib/labeling/helpers'
 import {track} from 'lib/analytics/analytics'
 
 export class ProfileViewerModel {
@@ -26,7 +22,8 @@ export class ProfileViewerModel {
   following?: string
   followedBy?: string
   blockedBy?: boolean
-  blocking?: string
+  blocking?: string;
+  [key: string]: unknown
 
   constructor() {
     makeAutoObservable(this)
@@ -53,7 +50,8 @@ export class ProfileModel {
   followsCount: number = 0
   postsCount: number = 0
   labels?: ComAtprotoLabelDefs.Label[] = undefined
-  viewer = new ProfileViewerModel()
+  viewer = new ProfileViewerModel();
+  [key: string]: unknown
 
   // added data
   descriptionRichText?: RichText = new RichText({text: ''})
@@ -85,25 +83,20 @@ export class ProfileModel {
     return this.hasLoaded && !this.hasContent
   }
 
-  get labelInfo(): ProfileLabelInfo {
-    return {
-      accountLabels: filterAccountLabels(this.labels),
-      profileLabels: filterProfileLabels(this.labels),
-      isMuted: this.viewer?.muted || false,
-      isBlocking: !!this.viewer?.blocking || false,
-      isBlockedBy: !!this.viewer?.blockedBy || false,
-    }
-  }
-
   get moderation(): ProfileModeration {
-    return getProfileModeration(this.rootStore, this.labelInfo)
+    return moderateProfile(this, this.rootStore.preferences.moderationOpts)
   }
 
   // public api
   // =
 
   async setup() {
-    await this._load()
+    const precache = await this.rootStore.profiles.cache.get(this.params.actor)
+    if (precache) {
+      await this._loadWithCache(precache)
+    } else {
+      await this._load()
+    }
   }
 
   async refresh() {
@@ -252,7 +245,13 @@ export class ProfileModel {
     this._xLoading(isRefreshing)
     try {
       const res = await this.rootStore.agent.getProfile(this.params)
-      this.rootStore.profiles.overwrite(this.params.actor, res) // cache invalidation
+      this.rootStore.profiles.overwrite(this.params.actor, res)
+      if (res.data.handle) {
+        this.rootStore.handleResolutions.cache.set(
+          res.data.handle,
+          res.data.did,
+        )
+      }
       this._replaceAll(res)
       await this._createRichText()
       this._xIdle()
@@ -261,6 +260,23 @@ export class ProfileModel {
     }
   }
 
+  async _loadWithCache(precache: GetProfile.Response) {
+    // use cached value
+    this._replaceAll(precache)
+    await this._createRichText()
+    this._xIdle()
+
+    // fetch latest
+    try {
+      const res = await this.rootStore.agent.getProfile(this.params)
+      this.rootStore.profiles.overwrite(this.params.actor, res) // cache invalidation
+      this._replaceAll(res)
+      await this._createRichText()
+    } catch (e: any) {
+      this._xIdle(e)
+    }
+  }
+
   _replaceAll(res: GetProfile.Response) {
     this.did = res.data.did
     this.handle = res.data.handle