From d1e9e74cb87463cb4b5d9c0f3a127e374a1a1e32 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 19 Feb 2020 06:16:49 +0900
Subject: [PATCH] Resolve #5978

---
 locales/ja-JP.yml                       |  1 +
 src/client/app.vue                      | 43 +++++++++------
 src/client/components/notifications.vue | 70 +++++++++++++------------
 src/client/pages/notifications.vue      | 42 +++++++++++++++
 src/client/pages/settings/index.vue     |  8 ++-
 src/client/router.ts                    |  1 +
 src/client/store.ts                     |  1 +
 7 files changed, 114 insertions(+), 52 deletions(-)
 create mode 100644 src/client/pages/notifications.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 117a8f997b..5fcd562720 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -423,6 +423,7 @@ serverLogs: "サーバーログ"
 deleteAll: "全て削除"
 showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
 newNoteRecived: "新しいノートがあります"
+useNotificationsPopup: "通知一覧をポップアップで表示"
 
 _ago:
   unknown: "謎"
diff --git a/src/client/app.vue b/src/client/app.vue
index 9b0745d195..a3290486a7 100644
--- a/src/client/app.vue
+++ b/src/client/app.vue
@@ -50,21 +50,27 @@
 				<router-link class="item index" active-class="active" to="/" exact v-else>
 					<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
 				</router-link>
-				<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.getters.isSignedIn">
-					<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
-					<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
-				</button>
-				<router-link class="item" active-class="active" to="/my/messaging" v-if="$store.getters.isSignedIn">
-					<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
-					<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
-				</router-link>
-				<router-link class="item" active-class="active" to="/my/drive" v-if="$store.getters.isSignedIn">
-					<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
-				</router-link>
-				<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
-					<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
-					<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
-				</router-link>
+				<template v-if="$store.getters.isSignedIn">
+					<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.state.device.useNotificationsPopup">
+						<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
+						<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
+					</button>
+					<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton" v-else>
+						<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
+						<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
+					</router-link>
+					<router-link class="item" active-class="active" to="/my/messaging">
+						<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
+						<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
+					</router-link>
+					<router-link class="item" active-class="active" to="/my/drive">
+						<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
+					</router-link>
+					<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.state.i.isLocked">
+						<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
+						<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
+					</router-link>
+				</template>
 				<div class="divider"></div>
 				<router-link class="item" active-class="active" to="/featured">
 					<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
@@ -143,7 +149,8 @@
 		<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button>
 		<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
 		<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
-		<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
+		<button v-if="$store.getters.isSignedIn && $store.state.device.useNotificationsPopup" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
+		<button v-if="$store.getters.isSignedIn && !$store.state.device.useNotificationsPopup" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
 		<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
 	</div>
 
@@ -1206,15 +1213,17 @@ export default Vue.extend({
 		left: 0;
 		right: 0;
 		margin: 0 auto;
+		padding: 8px 8px 0 8px;
 		z-index: 10001;
 		width: 350px;
 		height: 400px;
+		box-sizing: border-box;
 		background: var(--vocsgcxy);
 		-webkit-backdrop-filter: blur(12px);
 		backdrop-filter: blur(12px);
 		border-radius: 6px;
 		box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
-		overflow: hidden;
+		overflow: auto;
 
 		@media (max-width: 800px) {
 			width: 320px;
diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue
index ff2fc4af8a..3c2eb1bd51 100644
--- a/src/client/components/notifications.vue
+++ b/src/client/components/notifications.vue
@@ -1,19 +1,17 @@
 <template>
-<div class="mk-notifications">
-	<div class="contents">
-		<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
-			<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
-		</x-list>
+<div class="mk-notifications" :class="{ page }">
+	<x-list class="notifications" :items="items" v-slot="{ item: notification }">
+		<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :class="{ _panel: page }" :key="notification.id"/>
+	</x-list>
 
-		<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
-			<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
-			<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
-		</button>
+	<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
+		<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
+		<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
+	</button>
 
-		<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
+	<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
 
-		<mk-error v-if="error" @retry="init()"/>
-	</div>
+	<mk-error v-if="error" @retry="init()"/>
 </div>
 </template>
 
@@ -42,7 +40,7 @@ export default Vue.extend({
 			type: String,
 			required: false
 		},
-		wide: {
+		page: {
 			type: Boolean,
 			required: false,
 			default: false
@@ -93,11 +91,15 @@ export default Vue.extend({
 
 <style lang="scss" scoped>
 .mk-notifications {
-	> .contents {
-		overflow: auto;
-		height: 100%;
-		padding: 8px 8px 0 8px;
+	&.page {
+		> .notifications {
+			> ::v-deep * {
+				margin-bottom: var(--margin);
+			}
+		}
+	}
 
+	&:not(.page) {
 		> .notifications {
 			> ::v-deep * {
 				margin-bottom: 8px;
@@ -109,28 +111,28 @@ export default Vue.extend({
 				box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 			}
 		}
+	}
 
-		> .more {
-			display: block;
-			width: 100%;
-			padding: 16px;
+	> .more {
+		display: block;
+		width: 100%;
+		padding: 16px;
 
-			> [data-icon] {
-				margin-right: 4px;
-			}
+		> [data-icon] {
+			margin-right: 4px;
 		}
+	}
 
-		> .empty {
-			margin: 0;
-			padding: 16px;
-			text-align: center;
-			color: var(--fg);
-		}
+	> .empty {
+		margin: 0;
+		padding: 16px;
+		text-align: center;
+		color: var(--fg);
+	}
 
-		> .placeholder {
-			padding: 32px;
-			opacity: 0.3;
-		}
+	> .placeholder {
+		padding: 32px;
+		opacity: 0.3;
 	}
 }
 </style>
diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue
new file mode 100644
index 0000000000..49e67bc8f7
--- /dev/null
+++ b/src/client/pages/notifications.vue
@@ -0,0 +1,42 @@
+<template>
+<div>
+	<portal to="icon"><fa :icon="faBell"/></portal>
+	<portal to="title">{{ $t('notifications') }}</portal>
+	<x-notifications @before="before" @after="after" page/>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import { faBell } from '@fortawesome/free-solid-svg-icons';
+import Progress from '../scripts/loading';
+import XNotifications from '../components/notifications.vue';
+
+export default Vue.extend({
+	metaInfo() {
+		return {
+			title: this.$t('notifications') as string
+		};
+	},
+
+	components: {
+		XNotifications
+	},
+
+	data() {
+		return {
+			faBell
+		};
+	},
+
+	methods: {
+		before() {
+			Progress.start();
+		},
+
+		after() {
+			Progress.done();
+		}
+	}
+});
+</script>
diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue
index 986ade8ffc..b3ef4d17b9 100644
--- a/src/client/pages/settings/index.vue
+++ b/src/client/pages/settings/index.vue
@@ -20,7 +20,8 @@
 				{{ $t('useOsNativeEmojis') }}
 				<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
 			</mk-switch>
-			<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch>			
+			<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch>
+			<mk-switch v-model="useNotificationsPopup">{{ $t('useNotificationsPopup') }}</mk-switch>
 		</div>
 		<div class="_content">
 			<mk-select v-model="lang">
@@ -111,6 +112,11 @@ export default Vue.extend({
 			get() { return this.$store.state.device.showFixedPostForm; },
 			set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
 		},
+
+		useNotificationsPopup: {
+			get() { return this.$store.state.device.useNotificationsPopup; },
+			set(value) { this.$store.commit('device/set', { key: 'useNotificationsPopup', value }); }
+		},
 	},
 
 	watch: {
diff --git a/src/client/router.ts b/src/client/router.ts
index 0a856d580d..86ef4c7056 100644
--- a/src/client/router.ts
+++ b/src/client/router.ts
@@ -27,6 +27,7 @@ export const router = new VueRouter({
 		{ path: '/explore', component: page('explore') },
 		{ path: '/explore/tags/:tag', props: true, component: page('explore') },
 		{ path: '/search', component: page('search') },
+		{ path: '/my/notifications', component: page('notifications') },
 		{ path: '/my/favorites', component: page('favorites') },
 		{ path: '/my/messages', component: page('messages') },
 		{ path: '/my/mentions', component: page('mentions') },
diff --git a/src/client/store.ts b/src/client/store.ts
index 28c995132e..2d84b7b318 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -40,6 +40,7 @@ const defaultDeviceSettings = {
 	animatedMfm: true,
 	imageNewTab: false,
 	showFixedPostForm: false,
+	useNotificationsPopup: true,
 	userData: {},
 };