From f62ad3ed3eccfd242b2d1f1e25f00276f2bfff77 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 2 Nov 2023 15:57:55 +0900
Subject: feat: notification grouping

Resolve #12211
---
 .../frontend/src/components/MkNotification.vue     | 82 ++++++++++++++++++++--
 1 file changed, 78 insertions(+), 4 deletions(-)

(limited to 'packages/frontend/src/components/MkNotification.vue')

diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index c507236216..ff20bc591f 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -9,9 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
 		<MkAvatar v-else-if="notification.type === 'note'" :class="$style.icon" :user="notification.note.user" link preview/>
 		<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
+		<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
+		<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
 		<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
 		<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
-		<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
+		<img v-else-if="notification.icon" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
 		<div
 			:class="[$style.subIcon, {
 				[$style.t_follow]: notification.type === 'follow',
@@ -39,7 +41,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 				v-else-if="notification.type === 'reaction'"
 				ref="reactionRef"
 				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
-				:customEmojis="notification.note.emojis"
 				:noStyle="true"
 				style="width: 100%; height: 100%;"
 			/>
@@ -52,16 +53,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
 			<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
 			<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
+			<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.t('_notification.reactedBySomeUsers', { n: notification.reactions.length }) }}</span>
+			<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.t('_notification.renotedBySomeUsers', { n: notification.users.length }) }}</span>
 			<span v-else>{{ notification.header }}</span>
 			<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
 		</header>
 		<div>
-			<MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+			<MkA v-if="notification.type === 'reaction' || notification.type === 'reaction:grouped'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 				<i class="ti ti-quote" :class="$style.quote"></i>
 				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
 				<i class="ti ti-quote" :class="$style.quote"></i>
 			</MkA>
-			<MkA v-else-if="notification.type === 'renote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
+			<MkA v-else-if="notification.type === 'renote' || notification.type === 'renote:grouped'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
 				<i class="ti ti-quote" :class="$style.quote"></i>
 				<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :author="notification.note.renote.user"/>
 				<i class="ti ti-quote" :class="$style.quote"></i>
@@ -102,6 +105,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<span v-else-if="notification.type === 'app'" :class="$style.text">
 				<Mfm :text="notification.body" :nowrap="false"/>
 			</span>
+
+			<div v-if="notification.type === 'reaction:grouped'">
+				<div v-for="reaction of notification.reactions" :class="$style.reactionsItem">
+					<MkAvatar :class="$style.reactionsItemAvatar" :user="reaction.user" link preview/>
+					<div :class="$style.reactionsItemReaction">
+						<MkReactionIcon
+							:reaction="reaction.reaction ? reaction.reaction.replace(/^:(\w+):$/, ':$1@.:') : reaction.reaction"
+							:noStyle="true"
+							style="width: 100%; height: 100%;"
+						/>
+					</div>
+				</div>
+			</div>
+			<div v-else-if="notification.type === 'renote:grouped'">
+				<div v-for="user of notification.users" :class="$style.reactionsItem">
+					<MkAvatar :class="$style.reactionsItemAvatar" :user="user" link preview/>
+				</div>
+			</div>
 		</div>
 	</div>
 </div>
@@ -181,6 +202,29 @@ useTooltip(reactionRef, (showing) => {
 	display: block;
 	width: 100%;
 	height: 100%;
+}
+
+.icon_reactionGroup,
+.icon_renoteGroup {
+	display: grid;
+	align-items: center;
+	justify-items: center;
+	width: 80%;
+	height: 80%;
+	font-size: 15px;
+	border-radius: 100%;
+	color: #fff;
+}
+
+.icon_reactionGroup {
+	background: #e99a0b;
+}
+
+.icon_renoteGroup {
+	background: #36d298;
+}
+
+.icon_app {
 	border-radius: 6px;
 }
 
@@ -305,6 +349,36 @@ useTooltip(reactionRef, (showing) => {
 	flex: 1;
 }
 
+.reactionsItem {
+	display: inline-block;
+	position: relative;
+	width: 38px;
+	height: 38px;
+	margin-top: 8px;
+	margin-right: 8px;
+}
+
+.reactionsItemAvatar {
+	width: 100%;
+	height: 100%;
+}
+
+.reactionsItemReaction {
+	position: absolute;
+	z-index: 1;
+	bottom: -2px;
+	right: -2px;
+	width: 20px;
+	height: 20px;
+	box-sizing: border-box;
+	border-radius: 100%;
+	background: var(--panel);
+	box-shadow: 0 0 0 3px var(--panel);
+	font-size: 11px;
+	text-align: center;
+	color: #fff;
+}
+
 @container (max-width: 600px) {
 	.root {
 		padding: 16px;
-- 
cgit v1.2.3-freya

