about summary refs log tree commit diff
path: root/src/state/models/discovery/user-autocomplete.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/discovery/user-autocomplete.ts')
-rw-r--r--src/state/models/discovery/user-autocomplete.ts115
1 files changed, 76 insertions, 39 deletions
diff --git a/src/state/models/discovery/user-autocomplete.ts b/src/state/models/discovery/user-autocomplete.ts
index 461073e45..25ce859d2 100644
--- a/src/state/models/discovery/user-autocomplete.ts
+++ b/src/state/models/discovery/user-autocomplete.ts
@@ -4,6 +4,8 @@ import AwaitLock from 'await-lock'
 import {RootStoreModel} from '../root-store'
 import {isInvalidHandle} from 'lib/strings/handles'
 
+type ProfileViewBasic = AppBskyActorDefs.ProfileViewBasic
+
 export class UserAutocompleteModel {
   // state
   isLoading = false
@@ -12,9 +14,8 @@ export class UserAutocompleteModel {
   lock = new AwaitLock()
 
   // data
-  follows: AppBskyActorDefs.ProfileViewBasic[] = []
-  searchRes: AppBskyActorDefs.ProfileViewBasic[] = []
   knownHandles: Set<string> = new Set()
+  _suggestions: ProfileViewBasic[] = []
 
   constructor(public rootStore: RootStoreModel) {
     makeAutoObservable(
@@ -27,29 +28,35 @@ export class UserAutocompleteModel {
     )
   }
 
-  get suggestions() {
+  get follows(): ProfileViewBasic[] {
+    return Object.values(this.rootStore.me.follows.byDid).map(item => ({
+      did: item.did,
+      handle: item.handle,
+      displayName: item.displayName,
+      avatar: item.avatar,
+    }))
+  }
+
+  get suggestions(): ProfileViewBasic[] {
     if (!this.isActive) {
       return []
     }
-    if (this.prefix) {
-      return this.searchRes.map(user => ({
-        handle: user.handle,
-        displayName: user.displayName,
-        avatar: user.avatar,
-      }))
-    }
-    return this.follows.map(follow => ({
-      handle: follow.handle,
-      displayName: follow.displayName,
-      avatar: follow.avatar,
-    }))
+    return this._suggestions
   }
 
   // public api
   // =
 
   async setup() {
-    await this._getFollows()
+    await this.rootStore.me.follows.syncIfNeeded()
+    runInAction(() => {
+      for (const did in this.rootStore.me.follows.byDid) {
+        const info = this.rootStore.me.follows.byDid[did]
+        if (!isInvalidHandle(info.handle)) {
+          this.knownHandles.add(info.handle)
+        }
+      }
+    })
   }
 
   setActive(v: boolean) {
@@ -57,7 +64,7 @@ export class UserAutocompleteModel {
   }
 
   async setPrefix(prefix: string) {
-    const origPrefix = prefix.trim()
+    const origPrefix = prefix.trim().toLocaleLowerCase()
     this.prefix = origPrefix
     await this.lock.acquireAsync()
     try {
@@ -65,9 +72,27 @@ export class UserAutocompleteModel {
         if (this.prefix !== origPrefix) {
           return // another prefix was set before we got our chance
         }
-        await this._search()
+
+        // reset to follow results
+        this._computeSuggestions([])
+
+        // ask backend
+        const res = await this.rootStore.agent.searchActorsTypeahead({
+          term: this.prefix,
+          limit: 8,
+        })
+        this._computeSuggestions(res.data.actors)
+
+        // update known handles
+        runInAction(() => {
+          for (const u of res.data.actors) {
+            this.knownHandles.add(u.handle)
+          }
+        })
       } else {
-        this.searchRes = []
+        runInAction(() => {
+          this._computeSuggestions([])
+        })
       }
     } finally {
       this.lock.release()
@@ -77,28 +102,40 @@ export class UserAutocompleteModel {
   // internal
   // =
 
-  async _getFollows() {
-    const res = await this.rootStore.agent.getFollows({
-      actor: this.rootStore.me.did || '',
-    })
-    runInAction(() => {
-      this.follows = res.data.follows.filter(f => !isInvalidHandle(f.handle))
-      for (const f of this.follows) {
-        this.knownHandles.add(f.handle)
+  _computeSuggestions(searchRes: AppBskyActorDefs.ProfileViewBasic[] = []) {
+    if (this.prefix) {
+      const items: ProfileViewBasic[] = []
+      for (const item of this.follows) {
+        if (prefixMatch(this.prefix, item)) {
+          items.push(item)
+        }
+        if (items.length >= 8) {
+          break
+        }
       }
-    })
+      for (const item of searchRes) {
+        if (!items.find(item2 => item2.handle === item.handle)) {
+          items.push({
+            did: item.did,
+            handle: item.handle,
+            displayName: item.displayName,
+            avatar: item.avatar,
+          })
+        }
+      }
+      this._suggestions = items
+    } else {
+      this._suggestions = this.follows
+    }
   }
+}
 
-  async _search() {
-    const res = await this.rootStore.agent.searchActorsTypeahead({
-      term: this.prefix,
-      limit: 8,
-    })
-    runInAction(() => {
-      this.searchRes = res.data.actors
-      for (const u of this.searchRes) {
-        this.knownHandles.add(u.handle)
-      }
-    })
+function prefixMatch(prefix: string, info: ProfileViewBasic): boolean {
+  if (info.handle.includes(prefix)) {
+    return true
+  }
+  if (info.displayName?.toLocaleLowerCase().includes(prefix)) {
+    return true
   }
+  return false
 }