about summary refs log tree commit diff
path: root/src/state/models/content/post-thread.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/content/post-thread.ts')
-rw-r--r--src/state/models/content/post-thread.ts354
1 files changed, 0 insertions, 354 deletions
diff --git a/src/state/models/content/post-thread.ts b/src/state/models/content/post-thread.ts
deleted file mode 100644
index fd194056a..000000000
--- a/src/state/models/content/post-thread.ts
+++ /dev/null
@@ -1,354 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {
-  AppBskyFeedGetPostThread as GetPostThread,
-  AppBskyFeedDefs,
-  AppBskyFeedPost,
-  PostModeration,
-} from '@atproto/api'
-import {AtUri} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
-import * as apilib from 'lib/api/index'
-import {cleanError} from 'lib/strings/errors'
-import {ThreadViewPreference} from '../ui/preferences'
-import {PostThreadItemModel} from './post-thread-item'
-import {logger} from '#/logger'
-
-export class PostThreadModel {
-  // state
-  isLoading = false
-  isLoadingFromCache = false
-  isFromCache = false
-  isRefreshing = false
-  hasLoaded = false
-  error = ''
-  notFound = false
-  resolvedUri = ''
-  params: GetPostThread.QueryParams
-
-  // data
-  thread?: PostThreadItemModel | null = null
-  isBlocked = false
-
-  constructor(
-    public rootStore: RootStoreModel,
-    params: GetPostThread.QueryParams,
-  ) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-        params: false,
-      },
-      {autoBind: true},
-    )
-    this.params = params
-  }
-
-  static fromPostView(
-    rootStore: RootStoreModel,
-    postView: AppBskyFeedDefs.PostView,
-  ) {
-    const model = new PostThreadModel(rootStore, {uri: postView.uri})
-    model.resolvedUri = postView.uri
-    model.hasLoaded = true
-    model.thread = new PostThreadItemModel(rootStore, {
-      post: postView,
-    })
-    return model
-  }
-
-  get hasContent() {
-    return !!this.thread
-  }
-
-  get hasError() {
-    return this.error !== ''
-  }
-
-  get rootUri(): string {
-    if (this.thread) {
-      if (this.thread.postRecord?.reply?.root.uri) {
-        return this.thread.postRecord.reply.root.uri
-      }
-    }
-    return this.resolvedUri
-  }
-
-  get isThreadMuted() {
-    return this.rootStore.mutedThreads.uris.has(this.rootUri)
-  }
-
-  get isCachedPostAReply() {
-    if (AppBskyFeedPost.isRecord(this.thread?.post.record)) {
-      return !!this.thread?.post.record.reply
-    }
-    return false
-  }
-
-  // public api
-  // =
-
-  /**
-   * Load for first render
-   */
-  async setup() {
-    if (!this.resolvedUri) {
-      await this._resolveUri()
-    }
-
-    if (this.hasContent) {
-      await this.update()
-    } else {
-      const precache = this.rootStore.posts.cache.get(this.resolvedUri)
-      if (precache) {
-        await this._loadPrecached(precache)
-      } else {
-        await this._load()
-      }
-    }
-  }
-
-  /**
-   * Register any event listeners. Returns a cleanup function.
-   */
-  registerListeners() {
-    const sub = this.rootStore.onPostDeleted(this.onPostDeleted.bind(this))
-    return () => sub.remove()
-  }
-
-  /**
-   * Reset and load
-   */
-  async refresh() {
-    await this._load(true)
-  }
-
-  /**
-   * Update content in-place
-   */
-  async update() {
-    // NOTE: it currently seems that a full load-and-replace works fine for this
-    //       if the UI loses its place or has jarring re-arrangements, replace this
-    //       with a more in-place update
-    this._load()
-  }
-
-  /**
-   * Refreshes when posts are deleted
-   */
-  onPostDeleted(_uri: string) {
-    this.refresh()
-  }
-
-  async toggleThreadMute() {
-    if (this.isThreadMuted) {
-      this.rootStore.mutedThreads.uris.delete(this.rootUri)
-    } else {
-      this.rootStore.mutedThreads.uris.add(this.rootUri)
-    }
-  }
-
-  // state transitions
-  // =
-
-  _xLoading(isRefreshing = false) {
-    this.isLoading = true
-    this.isRefreshing = isRefreshing
-    this.error = ''
-    this.notFound = false
-  }
-
-  _xIdle(err?: any) {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = true
-    this.error = cleanError(err)
-    if (err) {
-      logger.error('Failed to fetch post thread', {error: err})
-    }
-    this.notFound = err instanceof GetPostThread.NotFoundError
-  }
-
-  // loader functions
-  // =
-
-  async _resolveUri() {
-    const urip = new AtUri(this.params.uri)
-    if (!urip.host.startsWith('did:')) {
-      try {
-        urip.host = await apilib.resolveName(this.rootStore, urip.host)
-      } catch (e: any) {
-        runInAction(() => {
-          this.error = e.toString()
-        })
-      }
-    }
-    runInAction(() => {
-      this.resolvedUri = urip.toString()
-    })
-  }
-
-  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
-    }
-    this._xLoading(isRefreshing)
-    try {
-      const res = await this.rootStore.agent.getPostThread(
-        Object.assign({}, this.params, {uri: this.resolvedUri}),
-      )
-      this._replaceAll(res)
-      this._xIdle()
-    } catch (e: any) {
-      console.log(e)
-      this._xIdle(e)
-    }
-  }
-
-  _replaceAll(res: GetPostThread.Response) {
-    this.isBlocked = AppBskyFeedDefs.isBlockedPost(res.data.thread)
-    if (this.isBlocked) {
-      return
-    }
-    pruneReplies(res.data.thread)
-    const thread = new PostThreadItemModel(
-      this.rootStore,
-      res.data.thread as AppBskyFeedDefs.ThreadViewPost,
-    )
-    thread._isHighlightedPost = true
-    thread.assignTreeModels(
-      res.data.thread as AppBskyFeedDefs.ThreadViewPost,
-      thread.uri,
-    )
-    sortThread(thread, this.rootStore.preferences.thread)
-    this.thread = thread
-  }
-}
-
-type MaybePost =
-  | AppBskyFeedDefs.ThreadViewPost
-  | AppBskyFeedDefs.NotFoundPost
-  | AppBskyFeedDefs.BlockedPost
-  | {[k: string]: unknown; $type: string}
-function pruneReplies(post: MaybePost) {
-  if (post.replies) {
-    post.replies = (post.replies as MaybePost[]).filter((reply: MaybePost) => {
-      if (reply.blocked) {
-        return false
-      }
-      pruneReplies(reply)
-      return true
-    })
-  }
-}
-
-type MaybeThreadItem =
-  | PostThreadItemModel
-  | AppBskyFeedDefs.NotFoundPost
-  | AppBskyFeedDefs.BlockedPost
-function sortThread(item: MaybeThreadItem, opts: ThreadViewPreference) {
-  if ('notFound' in item) {
-    return
-  }
-  item = item as PostThreadItemModel
-  if (item.replies) {
-    item.replies.sort((a: MaybeThreadItem, b: MaybeThreadItem) => {
-      if ('notFound' in a && a.notFound) {
-        return 1
-      }
-      if ('notFound' in b && b.notFound) {
-        return -1
-      }
-      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) {
-        return -1 // op's own reply
-      } 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)
-      }
-      if (opts.prioritizeFollowedUsers) {
-        const af = a.post.author.viewer?.following
-        const bf = b.post.author.viewer?.following
-        if (af && !bf) {
-          return -1
-        } else if (!af && bf) {
-          return 1
-        }
-      }
-      if (opts.sort === 'oldest') {
-        return a.post.indexedAt.localeCompare(b.post.indexedAt)
-      } else if (opts.sort === 'newest') {
-        return b.post.indexedAt.localeCompare(a.post.indexedAt)
-      } else if (opts.sort === 'most-likes') {
-        if (a.post.likeCount === b.post.likeCount) {
-          return b.post.indexedAt.localeCompare(a.post.indexedAt) // newest
-        } else {
-          return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes
-        }
-      } else if (opts.sort === 'random') {
-        return 0.5 - Math.random() // this is vaguely criminal but we can get away with it
-      }
-      return b.post.indexedAt.localeCompare(a.post.indexedAt)
-    })
-    item.replies.forEach(reply => sortThread(reply, opts))
-  }
-}
-
-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
-}