parent
							
								
									b8aad35009
								
							
						
					
					
						commit
						e88ce1746d
					
				
					 7 changed files with 257 additions and 17 deletions
				
			
		| 
						 | 
				
			
			@ -519,6 +519,14 @@ common/views/components/profile-editor.vue:
 | 
			
		|||
  email-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:
 | 
			
		||||
  fetching: "確認中"
 | 
			
		||||
  no-broadcasts: "お知らせはありません"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										150
									
								
								src/client/app/common/views/components/user-list-editor.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/client/app/common/views/components/user-list-editor.vue
									
										
									
									
									
										Normal 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +92,7 @@
 | 
			
		|||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import MkUserListsWindow from './user-lists-window.vue';
 | 
			
		||||
import MkUserListWindow from './user-list-window.vue';
 | 
			
		||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
 | 
			
		||||
import MkSettingsWindow from './settings-window.vue';
 | 
			
		||||
import MkDriveWindow from './drive-window.vue';
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +144,9 @@ export default Vue.extend({
 | 
			
		|||
			this.close();
 | 
			
		||||
			const w = this.$root.new(MkUserListsWindow);
 | 
			
		||||
			w.$once('choosen', list => {
 | 
			
		||||
				this.$router.push(`i/lists/${ list.id }`);
 | 
			
		||||
				this.$root.new(MkUserListWindow, {
 | 
			
		||||
					list
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		followRequests() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								src/client/app/desktop/views/components/user-list-window.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/client/app/desktop/views/components/user-list-window.vue
									
										
									
									
									
										Normal 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<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>
 | 
			
		||||
 | 
			
		||||
	<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,7 @@
 | 
			
		|||
	<span slot="header" v-if="!fetching"><fa icon="list"/>{{ list.title }}</span>
 | 
			
		||||
 | 
			
		||||
	<main v-if="!fetching">
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li v-for="user in users" :key="user.id"><router-link :to="user | userPage">
 | 
			
		||||
				<mk-user-name :user="user"/>
 | 
			
		||||
			</router-link></li>
 | 
			
		||||
		</ul>
 | 
			
		||||
		<x-editor :list="list"/>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,13 +11,16 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Progress from '../../../common/scripts/loading';
 | 
			
		||||
import XEditor from '../../../common/views/components/user-list-editor.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XEditor
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			list: null,
 | 
			
		||||
			users: null
 | 
			
		||||
			list: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
| 
						 | 
				
			
			@ -42,12 +41,6 @@ export default Vue.extend({
 | 
			
		|||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				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>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
main
 | 
			
		||||
	width 100%
 | 
			
		||||
	max-width 680px
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										64
									
								
								src/server/api/endpoints/users/lists/pull.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/server/api/endpoints/users/lists/pull.ts
									
										
									
									
									
										Normal 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));
 | 
			
		||||
}));
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue