From 617cd7d1f8a83a81dbb679005b60926efe80e33a Mon Sep 17 00:00:00 2001
From: FloatingGhost <hannah@coffee-and-dreams.uk>
Date: Tue, 5 Jul 2022 14:28:23 +0100
Subject: [PATCH 1/2] refactor list search

---
 src/components/list_edit/list_edit.js         | 30 +++---------
 src/components/list_edit/list_edit.vue        | 30 ++++++------
 src/components/list_new/list_new.js           | 30 +++---------
 src/components/list_new/list_new.vue          | 41 +++++++----------
 .../list_user_search/list_user_search.js      | 46 +++++++++++++++++++
 .../list_user_search/list_user_search.vue     | 44 ++++++++++++++++++
 src/i18n/en.json                              |  3 +-
 src/modules/statuses.js                       |  4 +-
 src/services/api/api.service.js               |  6 ++-
 9 files changed, 143 insertions(+), 91 deletions(-)
 create mode 100644 src/components/list_user_search/list_user_search.js
 create mode 100644 src/components/list_user_search/list_user_search.vue

diff --git a/src/components/list_edit/list_edit.js b/src/components/list_edit/list_edit.js
index f982f4d4..9d828506 100644
--- a/src/components/list_edit/list_edit.js
+++ b/src/components/list_edit/list_edit.js
@@ -1,5 +1,6 @@
 import { mapState, mapGetters } from 'vuex'
 import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import ListUserSearch from '../list_user_search/list_user_search.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -15,15 +16,14 @@ library.add(
 const ListNew = {
   components: {
     BasicUserCard,
-    UserAvatar
+    UserAvatar,
+    ListUserSearch
   },
   data () {
     return {
       title: '',
       userIds: [],
-      selectedUserIds: [],
-      loading: false,
-      query: ''
+      selectedUserIds: []
     }
   },
   created () {
@@ -47,13 +47,6 @@ const ListNew = {
     selectedUsers () {
       return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user)
     },
-    availableUsers () {
-      if (this.query.length !== 0) {
-        return this.users
-      } else {
-        return this.selectedUsers
-      }
-    },
     ...mapState({
       currentUser: state => state.users.currentUser
     }),
@@ -79,19 +72,8 @@ const ListNew = {
     removeUser (userId) {
       this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
     },
-    search (query) {
-      if (!query) {
-        this.loading = false
-        return
-      }
-
-      this.loading = true
-      this.userIds = []
-      this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: true })
-        .then(data => {
-          this.loading = false
-          this.userIds = data.accounts.map(a => a.id)
-        })
+    onResults (results) {
+      this.userIds = results
     },
     updateList () {
       this.$store.dispatch('setList', { id: this.id, title: this.title })
diff --git a/src/components/list_edit/list_edit.vue b/src/components/list_edit/list_edit.vue
index 98704062..af46c4b8 100644
--- a/src/components/list_edit/list_edit.vue
+++ b/src/components/list_edit/list_edit.vue
@@ -21,23 +21,23 @@
         :placeholder="$t('lists.title')"
       >
     </div>
-    <div class="input-wrap">
-      <div class="input-search">
-        <FAIcon
-          class="search-icon fa-scale-110 fa-old-padding"
-          icon="search"
-        />
-      </div>
-      <input
-        ref="search"
-        v-model="query"
-        :placeholder="$t('lists.search')"
-        @input="onInput"
-      >
-    </div>
     <div class="member-list">
       <div
-        v-for="user in availableUsers"
+        v-for="user in selectedUsers"
+        :key="user.id"
+        class="member"
+      >
+        <BasicUserCard
+          :user="user"
+          :class="isSelected(user) ? 'selected' : ''"
+          @click.capture.prevent="selectUser(user)"
+        />
+      </div>
+    </div>
+    <ListUserSearch @results="onResults" />
+    <div class="member-list">
+      <div
+        v-for="user in users"
         :key="user.id"
         class="member"
       >
diff --git a/src/components/list_new/list_new.js b/src/components/list_new/list_new.js
index e3e4aef0..07ababe5 100644
--- a/src/components/list_new/list_new.js
+++ b/src/components/list_new/list_new.js
@@ -1,6 +1,7 @@
 import { mapState, mapGetters } from 'vuex'
 import BasicUserCard from '../basic_user_card/basic_user_card.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
+import ListUserSearch from '../list_user_search/list_user_search.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faSearch,
@@ -15,15 +16,14 @@ library.add(
 const ListNew = {
   components: {
     BasicUserCard,
-    UserAvatar
+    UserAvatar,
+    ListUserSearch
   },
   data () {
     return {
       title: '',
       userIds: [],
-      selectedUserIds: [],
-      loading: false,
-      query: ''
+      selectedUserIds: []
     }
   },
   computed: {
@@ -33,13 +33,6 @@ const ListNew = {
     selectedUsers () {
       return this.selectedUserIds.map(userId => this.findUser(userId))
     },
-    availableUsers () {
-      if (this.query.length !== 0) {
-        return this.users
-      } else {
-        return this.selectedUsers
-      }
-    },
     ...mapState({
       currentUser: state => state.users.currentUser
     }),
@@ -68,19 +61,8 @@ const ListNew = {
     removeUser (userId) {
       this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
     },
-    search (query) {
-      if (!query) {
-        this.loading = false
-        return
-      }
-
-      this.loading = true
-      this.userIds = []
-      this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: true })
-        .then(data => {
-          this.loading = false
-          this.userIds = data.accounts.map(a => a.id)
-        })
+    onResults (results) {
+      this.userIds = results
     },
     createList () {
       // the API has two different endpoints for "creating a list with a name"
diff --git a/src/components/list_new/list_new.vue b/src/components/list_new/list_new.vue
index 9bd7c5a5..a713b852 100644
--- a/src/components/list_new/list_new.vue
+++ b/src/components/list_new/list_new.vue
@@ -21,23 +21,10 @@
         :placeholder="$t('lists.title')"
       >
     </div>
-    <div class="input-wrap">
-      <div class="input-search">
-        <FAIcon
-          class="search-icon fa-scale-110 fa-old-padding"
-          icon="search"
-        />
-      </div>
-      <input
-        ref="search"
-        v-model="query"
-        :placeholder="$t('lists.search')"
-        @input="onInput"
-      >
-    </div>
+
     <div class="member-list">
       <div
-        v-for="user in availableUsers"
+        v-for="user in selectedUsers"
         :key="user.id"
         class="member"
       >
@@ -48,6 +35,21 @@
         />
       </div>
     </div>
+    <ListUserSearch
+      @results="onResults"
+    />
+    <div
+      v-for="user in users"
+      :key="user.id"
+      class="member"
+    >
+      <BasicUserCard
+        :user="user"
+        :class="isSelected(user) ? 'selected' : ''"
+        @click.capture.prevent="selectUser(user)"
+      />
+    </div>
+
     <button
       :disabled="title && title.length === 0"
       class="btn button-default"
@@ -64,15 +66,6 @@
 @import '../../_variables.scss';
 
 .list-new {
-  .input-wrap {
-    display: flex;
-    margin: 0.7em 0.5em 0.7em 0.5em;
-
-    input {
-      width: 100%;
-    }
-  }
-
   .search-icon {
     margin-right: 0.3em;
   }
diff --git a/src/components/list_user_search/list_user_search.js b/src/components/list_user_search/list_user_search.js
new file mode 100644
index 00000000..ab349df3
--- /dev/null
+++ b/src/components/list_user_search/list_user_search.js
@@ -0,0 +1,46 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+  faSearch,
+  faChevronLeft
+} from '@fortawesome/free-solid-svg-icons'
+import { debounce } from 'lodash'
+import Checkbox from '../checkbox/checkbox.vue'
+
+library.add(
+  faSearch,
+  faChevronLeft
+)
+
+const ListUserSearch = {
+  components: {
+    Checkbox
+  },
+  data () {
+    return {
+      loading: false,
+      query: '',
+      followingOnly: true
+    }
+  },
+  methods: {
+    onInput: debounce(function () {
+      this.search(this.query)
+    }, 2000),
+    search (query) {
+      if (!query) {
+        this.loading = false
+        return
+      }
+
+      this.loading = true
+      this.userIds = []
+      this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly })
+        .then(data => {
+          this.loading = false
+          this.$emit('results', data.accounts.map(a => a.id))
+        })
+    }
+  }
+}
+
+export default ListUserSearch
diff --git a/src/components/list_user_search/list_user_search.vue b/src/components/list_user_search/list_user_search.vue
new file mode 100644
index 00000000..988a0f68
--- /dev/null
+++ b/src/components/list_user_search/list_user_search.vue
@@ -0,0 +1,44 @@
+<template>
+  <div>
+    <div class="input-wrap">
+      <div class="input-search">
+        <FAIcon
+          class="search-icon fa-scale-110 fa-old-padding"
+          icon="search"
+        />
+      </div>
+      <input
+        ref="search"
+        v-model="query"
+        :placeholder="$t('lists.search')"
+        @input="onInput"
+      >
+    </div>
+    <div class="input-wrap">
+      <Checkbox
+        v-model="followingOnly"
+      >
+        {{ $t('lists.following_only') }}
+      </Checkbox>
+    </div>
+  </div>
+</template>
+
+<script src="./list_user_search.js"></script>
+<style lang="scss">
+@import '../../_variables.scss';
+
+.input-wrap {
+  display: flex;
+  margin: 0.7em 0.5em 0.7em 0.5em;
+
+  input {
+    width: 100%;
+  }
+}
+
+.search-icon {
+  margin-right: 0.3em;
+}
+
+</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 71fc3c47..db9625ad 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -970,7 +970,8 @@
     "search": "Search users",
     "create": "Create",
     "save": "Save changes",
-    "delete": "Delete list"
+    "delete": "Delete list",
+    "following_only": "Limit to Following"
   },
   "file_type": {
     "audio": "Audio",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index ea48f2d6..c1aa2e73 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -747,8 +747,8 @@ const statuses = {
       rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
         .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
     },
-    search (store, { q, resolve, limit, offset, following }) {
-      return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following })
+    search (store, { q, resolve, limit, offset, following, type }) {
+      return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following, type })
         .then((data) => {
           store.commit('addNewUsers', data.accounts)
           store.commit('addNewStatuses', { statuses: data.statuses })
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index bd263d83..8cf2a763 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1188,7 +1188,7 @@ const searchUsers = ({ credentials, query }) => {
     .then((data) => data.map(parseUser))
 }
 
-const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
+const search2 = ({ credentials, q, resolve, limit, offset, following, type }) => {
   let url = MASTODON_SEARCH_2
   let params = []
 
@@ -1212,6 +1212,10 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
     params.push(['following', true])
   }
 
+  if (type) {
+    params.push(['type', type])
+  }
+
   params.push(['with_relationships', true])
 
   let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')

From 88d04f0af7560e8ea9bc99c8bd65ac0eb0efbb34 Mon Sep 17 00:00:00 2001
From: FloatingGhost <hannah@coffee-and-dreams.uk>
Date: Tue, 5 Jul 2022 14:37:26 +0100
Subject: [PATCH 2/2] also run search on checkbox update

---
 src/components/list_user_search/list_user_search.vue | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/list_user_search/list_user_search.vue b/src/components/list_user_search/list_user_search.vue
index 988a0f68..fcd2f7da 100644
--- a/src/components/list_user_search/list_user_search.vue
+++ b/src/components/list_user_search/list_user_search.vue
@@ -17,6 +17,7 @@
     <div class="input-wrap">
       <Checkbox
         v-model="followingOnly"
+        @change="onInput"
       >
         {{ $t('lists.following_only') }}
       </Checkbox>