リスト関連の操作を強化

Resolve #2069
Resolve #2051
Resolve #2807
Resolve #3647
This commit is contained in:
syuilo 2018-12-19 07:22:01 +09:00
parent b8aad35009
commit e88ce1746d
No known key found for this signature in database
GPG key ID: BDC4C49D06AB9D69
7 changed files with 257 additions and 17 deletions

View file

@ -519,6 +519,14 @@ common/views/components/profile-editor.vue:
email-verified: "メールアドレスが確認されました" email-verified: "メールアドレスが確認されました"
email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。" email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
delete: "リストを削除"
remove-user: "このリストから削除"
delete-are-you-sure: "リスト「$1」を削除しますか"
deleted: "削除しました"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "確認中" fetching: "確認中"
no-broadcasts: "お知らせはありません" no-broadcasts: "お知らせはありません"

View file

@ -0,0 +1,150 @@
<template>
<div class="cudqjmnl">
<ui-card>
<div slot="title"><fa :icon="faList"/> {{ list.title }}</div>
<section>
<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
</section>
</ui-card>
<ui-card>
<div slot="title"><fa :icon="faUsers"/> {{ $t('users') }}</div>
<section>
<sequential-entrance animation="entranceFromTop" delay="25">
<div class="phcqulfl" v-for="user in users">
<div>
<a :href="user | userPage">
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
</a>
</div>
<div>
<header>
<b><mk-user-name :user="user"/></b>
<span class="username">@{{ user | acct }}</span>
</header>
<div>
<a @click="remove(user)">{{ $t('remove-user') }}</a>
</div>
</div>
</div>
</sequential-entrance>
</section>
</ui-card>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { faList, faICursor, faUsers } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/user-list-editor.vue'),
props: {
list: {
required: true
}
},
data() {
return {
users: [],
faList, faICursor, faTrashAlt, faUsers
};
},
mounted() {
this.fetchUsers();
},
methods: {
fetchUsers() {
this.$root.api('users/show', {
userIds: this.list.userIds
}).then(users => {
this.users = users;
});
},
rename() {
this.$root.dialog({
title: this.$t('rename'),
input: {
default: this.list.title
}
}).then(({ canceled, result: title }) => {
if (canceled) return;
this.$root.api('users/lists/update', {
listId: this.list.id,
title: title
});
});
},
del() {
this.$root.dialog({
type: 'warning',
text: this.$t('delete-are-you-sure').replace('$1', this.list.title),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
this.$root.api('users/lists/delete', {
listId: this.list.id
}).then(() => {
this.$root.dialog({
type: 'success',
text: this.$t('deleted')
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
});
},
remove(user: any) {
this.$root.api('users/lists/pull', {
listId: this.list.id,
userId: user.id
}).then(() => {
this.fetchUsers();
});
}
}
});
</script>
<style lang="stylus" scoped>
.cudqjmnl
.phcqulfl
display flex
padding 16px 0
border-top solid 1px var(--faceDivider)
> div:first-child
> a
> .avatar
width 64px
height 64px
> div:last-child
flex 1
padding-left 16px
@media (max-width 500px)
font-size 14px
> header
> .username
margin-left 8px
opacity 0.7
</style>

View file

@ -92,6 +92,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import MkUserListsWindow from './user-lists-window.vue'; import MkUserListsWindow from './user-lists-window.vue';
import MkUserListWindow from './user-list-window.vue';
import MkFollowRequestsWindow from './received-follow-requests-window.vue'; import MkFollowRequestsWindow from './received-follow-requests-window.vue';
import MkSettingsWindow from './settings-window.vue'; import MkSettingsWindow from './settings-window.vue';
import MkDriveWindow from './drive-window.vue'; import MkDriveWindow from './drive-window.vue';
@ -143,7 +144,9 @@ export default Vue.extend({
this.close(); this.close();
const w = this.$root.new(MkUserListsWindow); const w = this.$root.new(MkUserListsWindow);
w.$once('choosen', list => { w.$once('choosen', list => {
this.$router.push(`i/lists/${ list.id }`); this.$root.new(MkUserListWindow, {
list
});
}); });
}, },
followRequests() { followRequests() {

View file

@ -0,0 +1,24 @@
<template>
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
<span slot="header"><fa icon="list"/> {{ list.title }}</span>
<x-editor :list="list"/>
</mk-window>
</template>
<script lang="ts">
import Vue from 'vue';
import XEditor from '../../../common/views/components/user-list-editor.vue';
export default Vue.extend({
components: {
XEditor
},
props: {
list: {
required: true
}
}
});
</script>

View file

@ -1,5 +1,5 @@
<template> <template>
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom"> <mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
<span slot="header"><fa icon="list"/> {{ $t('title') }}</span> <span slot="header"><fa icon="list"/> {{ $t('title') }}</span>
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp"> <div class="xkxvokkjlptzyewouewmceqcxhpgzprp">

View file

@ -3,11 +3,7 @@
<span slot="header" v-if="!fetching"><fa icon="list"/>{{ list.title }}</span> <span slot="header" v-if="!fetching"><fa icon="list"/>{{ list.title }}</span>
<main v-if="!fetching"> <main v-if="!fetching">
<ul> <x-editor :list="list"/>
<li v-for="user in users" :key="user.id"><router-link :to="user | userPage">
<mk-user-name :user="user"/>
</router-link></li>
</ul>
</main> </main>
</mk-ui> </mk-ui>
</template> </template>
@ -15,13 +11,16 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import XEditor from '../../../common/views/components/user-list-editor.vue';
export default Vue.extend({ export default Vue.extend({
components: {
XEditor
},
data() { data() {
return { return {
fetching: true, fetching: true,
list: null, list: null
users: null
}; };
}, },
watch: { watch: {
@ -42,12 +41,6 @@ export default Vue.extend({
this.fetching = false; this.fetching = false;
Progress.done(); Progress.done();
this.$root.api('users/show', {
userIds: this.list.userIds
}).then(users => {
this.users = users;
});
}); });
} }
} }
@ -55,8 +48,6 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
main main
width 100% width 100%
max-width 680px max-width 680px

View file

@ -0,0 +1,64 @@
import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
import UserList from '../../../../../models/user-list';
import User, { pack as packUser } from '../../../../../models/user';
import { publishUserListStream } from '../../../../../stream';
import define from '../../../define';
export const meta = {
desc: {
'ja-JP': '指定したユーザーリストから指定したユーザーを削除します。',
'en-US': 'Remove a user to a user list.'
},
requireCredential: true,
kind: 'account-write',
params: {
listId: {
validator: $.type(ID),
transform: transform,
},
userId: {
validator: $.type(ID),
transform: transform,
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
},
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
// Fetch the list
const userList = await UserList.findOne({
_id: ps.listId,
userId: me._id,
});
if (userList == null) {
return rej('list not found');
}
// Fetch the user
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
}
// Push the user
await UserList.update({ _id: userList._id }, {
$pull: {
userIds: user._id
}
});
res();
publishUserListStream(userList._id, 'userRemoved', await packUser(user));
}));