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>