refactor list search #27

Merged
floatingghost merged 2 commits from list-searcher into develop 2022-07-05 13:42:18 +00:00
9 changed files with 144 additions and 91 deletions

View file

@ -1,5 +1,6 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' 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 UserAvatar from '../user_avatar/user_avatar.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -15,15 +16,14 @@ library.add(
const ListNew = { const ListNew = {
components: { components: {
BasicUserCard, BasicUserCard,
UserAvatar UserAvatar,
ListUserSearch
}, },
data () { data () {
return { return {
title: '', title: '',
userIds: [], userIds: [],
selectedUserIds: [], selectedUserIds: []
loading: false,
query: ''
} }
}, },
created () { created () {
@ -47,13 +47,6 @@ const ListNew = {
selectedUsers () { selectedUsers () {
return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user) 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({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}), }),
@ -79,19 +72,8 @@ const ListNew = {
removeUser (userId) { removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId) this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
}, },
search (query) { onResults (results) {
if (!query) { this.userIds = results
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)
})
}, },
updateList () { updateList () {
this.$store.dispatch('setList', { id: this.id, title: this.title }) this.$store.dispatch('setList', { id: this.id, title: this.title })

View file

@ -21,23 +21,23 @@
:placeholder="$t('lists.title')" :placeholder="$t('lists.title')"
> >
</div> </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 class="member-list">
<div <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" :key="user.id"
class="member" class="member"
> >

View file

@ -1,6 +1,7 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faSearch, faSearch,
@ -15,15 +16,14 @@ library.add(
const ListNew = { const ListNew = {
components: { components: {
BasicUserCard, BasicUserCard,
UserAvatar UserAvatar,
ListUserSearch
}, },
data () { data () {
return { return {
title: '', title: '',
userIds: [], userIds: [],
selectedUserIds: [], selectedUserIds: []
loading: false,
query: ''
} }
}, },
computed: { computed: {
@ -33,13 +33,6 @@ const ListNew = {
selectedUsers () { selectedUsers () {
return this.selectedUserIds.map(userId => this.findUser(userId)) return this.selectedUserIds.map(userId => this.findUser(userId))
}, },
availableUsers () {
if (this.query.length !== 0) {
return this.users
} else {
return this.selectedUsers
}
},
...mapState({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}), }),
@ -68,19 +61,8 @@ const ListNew = {
removeUser (userId) { removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId) this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
}, },
search (query) { onResults (results) {
if (!query) { this.userIds = results
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)
})
}, },
createList () { createList () {
// the API has two different endpoints for "creating a list with a name" // the API has two different endpoints for "creating a list with a name"

View file

@ -21,23 +21,10 @@
:placeholder="$t('lists.title')" :placeholder="$t('lists.title')"
> >
</div> </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 class="member-list">
<div <div
v-for="user in availableUsers" v-for="user in selectedUsers"
:key="user.id" :key="user.id"
class="member" class="member"
> >
@ -48,6 +35,21 @@
/> />
</div> </div>
</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 <button
:disabled="title && title.length === 0" :disabled="title && title.length === 0"
class="btn button-default" class="btn button-default"
@ -64,15 +66,6 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.list-new { .list-new {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
input {
width: 100%;
}
}
.search-icon { .search-icon {
margin-right: 0.3em; margin-right: 0.3em;
} }

View file

@ -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

View file

@ -0,0 +1,45 @@
<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"
@change="onInput"
>
{{ $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>

View file

@ -970,7 +970,8 @@
"search": "Search users", "search": "Search users",
"create": "Create", "create": "Create",
"save": "Save changes", "save": "Save changes",
"delete": "Delete list" "delete": "Delete list",
"following_only": "Limit to Following"
}, },
"file_type": { "file_type": {
"audio": "Audio", "audio": "Audio",

View file

@ -747,8 +747,8 @@ const statuses = {
rootState.api.backendInteractor.fetchRebloggedByUsers({ id }) rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
.then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })) .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
}, },
search (store, { q, resolve, limit, offset, following }) { search (store, { q, resolve, limit, offset, following, type }) {
return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following }) return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following, type })
.then((data) => { .then((data) => {
store.commit('addNewUsers', data.accounts) store.commit('addNewUsers', data.accounts)
store.commit('addNewStatuses', { statuses: data.statuses }) store.commit('addNewStatuses', { statuses: data.statuses })

View file

@ -1188,7 +1188,7 @@ const searchUsers = ({ credentials, query }) => {
.then((data) => data.map(parseUser)) .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 url = MASTODON_SEARCH_2
let params = [] let params = []
@ -1212,6 +1212,10 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
params.push(['following', true]) params.push(['following', true])
} }
if (type) {
params.push(['type', type])
}
params.push(['with_relationships', true]) params.push(['with_relationships', true])
let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')