From 8e4ad4b9195f168d66d3ce09a12afe736ceb481c Mon Sep 17 00:00:00 2001 From: Satsuki Yanagi <17376330+u1-liquid@users.noreply.github.com> Date: Wed, 17 Jul 2019 18:59:10 +0900 Subject: [PATCH] Improve usability of users view (#5176) * Improve usability of users view Resolve #5173 * Fix query * Follow review and fix * Follow review --- locales/ja-JP.yml | 2 ++ src/client/app/admin/views/users.user.vue | 4 +-- src/client/app/admin/views/users.vue | 33 +++++++++++++++++--- src/server/api/endpoints/admin/show-users.ts | 18 +++++++++++ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1c3f22dc2c..76fc26381f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1502,6 +1502,8 @@ admin/views/users.vue: remote-user-updated: "リモートユーザー情報を更新しました" delete-all-files: "すべてのファイルを削除" delete-all-files-confirm: "すべてのファイルを削除しますか?" + username: "ユーザー名" + host: "ホスト" users: title: "ユーザー" sort: diff --git a/src/client/app/admin/views/users.user.vue b/src/client/app/admin/views/users.user.vue index 929fc8f4b3..25ae0f3f52 100644 --- a/src/client/app/admin/views/users.user.vue +++ b/src/client/app/admin/views/users.user.vue @@ -1,5 +1,5 @@ <template> -<div class="kofvwchc"> +<div class="kofvwchc" @click="click(user.id)"> <div> <a :href="user | userPage(null, true)"> <mk-avatar class="avatar" :user="user" :disable-link="true"/> @@ -32,7 +32,7 @@ import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; export default Vue.extend({ i18n: i18n('admin/views/users.vue'), - props: ['user'], + props: ['user', 'click'], data() { return { faSnowflake, faMicrophoneSlash diff --git a/src/client/app/admin/views/users.vue b/src/client/app/admin/views/users.vue index fd9f0dd8b2..57377c7cee 100644 --- a/src/client/app/admin/views/users.vue +++ b/src/client/app/admin/views/users.vue @@ -8,7 +8,7 @@ </ui-input> <ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button> - <div class="user" v-if="user"> + <div ref="user" class="user" v-if="user" :key="user.id"> <x-user :user="user"/> <div class="actions"> <ui-button v-if="user.host != null" @click="updateRemoteUser"><fa :icon="faSync"/> {{ $t('update-remote-user') }}</ui-button> @@ -54,8 +54,16 @@ <option value="remote">{{ $t('users.origin.remote') }}</option> </ui-select> </ui-horizon-group> + <ui-horizon-group searchboxes> + <ui-input v-model="searchUsername" type="text" spellcheck="false" @input="fetchUsers(true)"> + <span>{{ $t('username') }}</span> + </ui-input> + <ui-input v-model="searchHost" type="text" spellcheck="false" @input="fetchUsers(true)" :readonly="origin === 'local'"> + <span>{{ $t('host') }}</span> + </ui-input> + </ui-horizon-group> <sequential-entrance animation="entranceFromTop" delay="25"> - <x-user v-for="user in users" :user='user' :key="user.id"/> + <x-user v-for="user in users" :key="user.id" :user='user' :click="showUserOnClick"/> </sequential-entrance> <ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button> </section> @@ -85,6 +93,8 @@ export default Vue.extend({ sort: '+createdAt', state: 'all', origin: 'local', + searchUsername: '', + searchHost: '', limit: 10, offset: 0, users: [], @@ -107,6 +117,7 @@ export default Vue.extend({ }, origin() { + if (this.origin === 'local') this.searchHost = ''; this.users = []; this.offset = 0; this.fetchUsers(); @@ -157,6 +168,15 @@ export default Vue.extend({ this.target = ''; }, + async showUserOnClick(userId: string) { + this.$root.api('admin/show-user', { userId: userId }).then(info => { + this.user = info; + this.$nextTick(() => { + this.$refs.user.scrollIntoView(); + }); + }); + }, + /** 処理対象ユーザーの情報を更新する */ async refreshUser() { this.$root.api('admin/show-user', { userId: this.user.id }).then(info => { @@ -308,13 +328,16 @@ export default Vue.extend({ return !confirm.canceled; }, - fetchUsers() { + fetchUsers(truncate?: boolean) { + if (truncate) this.offset = 0; this.$root.api('admin/show-users', { state: this.state, origin: this.origin, sort: this.sort, offset: this.offset, - limit: this.limit + 1 + limit: this.limit + 1, + username: this.searchUsername, + hostname: this.searchHost }).then(users => { if (users.length == this.limit + 1) { users.pop(); @@ -322,7 +345,7 @@ export default Vue.extend({ } else { this.existMore = false; } - this.users = this.users.concat(users); + this.users = truncate ? users : this.users.concat(users); this.offset += this.limit; }); } diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts index 8733d87a38..89e0cf1e2a 100644 --- a/src/server/api/endpoints/admin/show-users.ts +++ b/src/server/api/endpoints/admin/show-users.ts @@ -49,6 +49,16 @@ export const meta = { 'remote', ]), default: 'local' + }, + + username: { + validator: $.optional.str, + default: null + }, + + hostname: { + validator: $.optional.str, + default: null } } }; @@ -70,6 +80,14 @@ export default define(meta, async (ps, me) => { case 'remote': query.andWhere('user.host IS NOT NULL'); break; } + if (ps.username) { + query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); + } + + if (ps.hostname) { + query.andWhere('user.host like :hostname', { hostname: '%' + ps.hostname.toLowerCase() + '%' }); + } + switch (ps.sort) { case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break;