From d95242cab09119541ec6a14428252175a6cb5e54 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Sun, 15 Dec 2019 03:42:33 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=9F=E3=83=A5=E3=83=BC=E3=83=88/=E3=83=96?=
 =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E3=81=A7=E3=83=9A=E3=83=BC=E3=82=B8?=
 =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=81=A8=E8=A7=A3=E9=99=A4=E3=81=8C=E3=81=A7?=
 =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#5610)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 locales/ja-JP.yml                             |   2 +
 .../settings/mute-and-block.user.vue          |  39 ++++++
 .../components/settings/mute-and-block.vue    | 130 ++++++++++++++++--
 3 files changed, 157 insertions(+), 14 deletions(-)
 create mode 100644 src/client/app/common/views/components/settings/mute-and-block.user.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b1fe0be4d2..e0fb5087af 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1225,6 +1225,8 @@ common/views/components/mute-and-block.vue:
   word-mute: "ワードミュート"
   muted-words: "ミュートされたキーワード"
   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
+  unmute-confirm: "このユーザーをミュート解除しますか?"
+  unblock-confirm: "このユーザーをブロック解除しますか?"
   save: "保存"
 
 common/views/components/password-settings.vue:
diff --git a/src/client/app/common/views/components/settings/mute-and-block.user.vue b/src/client/app/common/views/components/settings/mute-and-block.user.vue
new file mode 100644
index 0000000000..29ef1f7a67
--- /dev/null
+++ b/src/client/app/common/views/components/settings/mute-and-block.user.vue
@@ -0,0 +1,39 @@
+<template>
+<div class="muteblockuser">
+	<div class="avatar-link">
+		<a :href="user | userPage(null, true)">
+			<mk-avatar class="avatar" :user="user" :disable-link="true"/>
+		</a>
+	</div>
+	<div class="text">
+		<div><mk-user-name :user="user"/></div>
+		<div class="username">@{{ user | acct }}</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../../i18n';
+
+export default Vue.extend({
+	i18n: i18n('common/views/components/mute-and-block.user.vue'),
+	props: ['user'],
+});
+</script>
+
+<style lang="stylus" scoped>
+.muteblockuser
+	display flex
+	padding 16px
+
+	> .avatar-link
+		> a
+			> .avatar
+				width 40px
+				height 40px
+
+	> .text
+		color var(--text)
+		margin-left 16px
+</style>
diff --git a/src/client/app/common/views/components/settings/mute-and-block.vue b/src/client/app/common/views/components/settings/mute-and-block.vue
index 33b19582b7..8ff5804168 100644
--- a/src/client/app/common/views/components/settings/mute-and-block.vue
+++ b/src/client/app/common/views/components/settings/mute-and-block.vue
@@ -6,9 +6,13 @@
 		<header>{{ $t('mute') }}</header>
 		<ui-info v-if="!muteFetching && mute.length == 0">{{ $t('no-muted-users') }}</ui-info>
 		<div class="users" v-if="mute.length != 0">
-			<div v-for="user in mute" :key="user.id">
-				<p><b><mk-user-name :user="user"/></b> @{{ user | acct }}</p>
+			<div class="user" v-for="user in mute" :key="user.id">
+				<x-user :user="user"/>
+				<span @click="unmute(user)">
+					<fa icon="times"/>
+				</span>
 			</div>
+			<ui-button v-if="this.muteCursor != null" @click="updateMute()">{{ $t('@.load-more') }}</ui-button>
 		</div>
 	</section>
 
@@ -16,9 +20,13 @@
 		<header>{{ $t('block') }}</header>
 		<ui-info v-if="!blockFetching && block.length == 0">{{ $t('no-blocked-users') }}</ui-info>
 		<div class="users" v-if="block.length != 0">
-			<div v-for="user in block" :key="user.id">
-				<p><b><mk-user-name :user="user"/></b> @{{ user | acct }}</p>
+			<div class="user" v-for="user in block" :key="user.id">
+				<x-user :user="user"/>
+				<span @click="unblock(user)">
+					<fa icon="times"/>
+				</span>
 			</div>
+			<ui-button v-if="this.blockCursor != null" @click="updateBlock()">{{ $t('@.load-more') }}</ui-button>
 		</div>
 	</section>
 
@@ -35,16 +43,25 @@
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../../../i18n';
+import XUser from './mute-and-block.user.vue';
+
+const fetchLimit = 30;
 
 export default Vue.extend({
 	i18n: i18n('common/views/components/mute-and-block.vue'),
 
+	components: {
+		XUser
+	},
+
 	data() {
 		return {
 			muteFetching: true,
 			blockFetching: true,
 			mute: [],
 			block: [],
+			muteCursor: undefined,
+			blockCursor: undefined,
 			mutedWords: ''
 		};
 	},
@@ -59,21 +76,106 @@ export default Vue.extend({
 	mounted() {
 		this.mutedWords = this._mutedWords.map(words => words.join(' ')).join('\n');
 
-		this.$root.api('mute/list').then(mute => {
-			this.mute = mute.map(x => x.mutee);
-			this.muteFetching = false;
-		});
-
-		this.$root.api('blocking/list').then(blocking => {
-			this.block = blocking.map(x => x.blockee);
-			this.blockFetching = false;
-		});
+		this.updateMute();
+		this.updateBlock();
 	},
 
 	methods: {
 		save() {
 			this._mutedWords = this.mutedWords.split('\n').map(line => line.split(' ').filter(x => x != ''));
-		}
+		},
+
+		unmute(user) {
+			this.$root.dialog({
+				type: 'warning',
+				text: this.$t('unmute-confirm'),
+				showCancelButton: true
+			}).then(({ canceled }) => {
+				if (canceled) return;
+				this.$root.api('mute/delete', {
+					userId: user.id
+				}).then(() => {
+					this.muteCursor = undefined;
+					this.updateMute();
+				});
+			});
+		},
+
+		unblock(user) {
+			this.$root.dialog({
+				type: 'warning',
+				text: this.$t('unblock-confirm'),
+				showCancelButton: true
+			}).then(({ canceled }) => {
+				if (canceled) return;
+				this.$root.api('blocking/delete', {
+					userId: user.id
+				}).then(() => {
+					this.updateBlock();
+				});
+			});
+		},
+
+		updateMute() {
+			this.muteFetching = true;
+			this.$root.api('mute/list', {
+				limit: fetchLimit + 1,
+				untilId: this.muteCursor,
+			}).then((items: Object[]) => {
+				const past = this.muteCursor ? this.mute : [];
+
+				if (items.length === fetchLimit + 1) {
+					items.pop()
+					this.muteCursor = items[items.length - 1].id;
+				} else {
+					this.muteCursor = undefined;
+				}
+
+				this.mute = past.concat(items.map(x => x.mutee));
+				this.muteFetching = false;
+			});
+		},
+
+		updateBlock() {
+			this.blockFetching = true;
+			this.$root.api('blocking/list', {
+				limit: fetchLimit + 1,
+				untilId: this.blockCursor,
+			}).then((items: Object[]) => {
+				const past = this.blockCursor ? this.block : [];
+
+				if (items.length === fetchLimit + 1) {
+					items.pop()
+					this.blockCursor = items[items.length - 1].id;
+				} else {
+					this.blockCursor = undefined;
+				}
+
+				this.block = past.concat(items.map(x => x.blockee));
+				this.blockFetching = false;
+			});
+		},
 	}
 });
 </script>
+
+<style lang="stylus" scoped>
+	.users
+		> .user
+			display flex
+			align-items center
+			justify-content flex-end
+			border-radius 6px
+
+			&:hover
+				background-color var(--primary)
+
+			> span
+				margin-left auto
+				cursor pointer
+				padding 16px
+		
+		> button
+			margin-top 16px
+</style>
+