Skip to content

Commit 7ead98c

Browse files
enhance(frontend): リアクションの総数を表示するように (#13532)
* enhance(frontend): リアクションの総数を表示するように * Update Changelog * リアクション選択済の色をaccentに
1 parent 6292235 commit 7ead98c

File tree

14 files changed

+79
-29
lines changed

14 files changed

+79
-29
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
### Client
77
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
8+
- Enhance: リアクション・いいねの総数を表示するように
9+
- Enhance: リアクション受け入れが「いいねのみ」の場合はリアクション絵文字一覧を表示しないように
810
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
911

1012
### Server

locales/index.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -8909,6 +8909,10 @@ export interface Locale extends ILocale {
89098909
* {n}人がリアクションしました
89108910
*/
89118911
"reactedBySomeUsers": ParameterizedString<"n">;
8912+
/**
8913+
* {n}人がいいねしました
8914+
*/
8915+
"likedBySomeUsers": ParameterizedString<"n">;
89128916
/**
89138917
* {n}人がリノートしました
89148918
*/

locales/ja-JP.yml

+1
Original file line numberDiff line numberDiff line change
@@ -2355,6 +2355,7 @@ _notification:
23552355
sendTestNotification: "テスト通知を送信する"
23562356
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
23572357
reactedBySomeUsers: "{n}人がリアクションしました"
2358+
likedBySomeUsers: "{n}人がいいねしました"
23582359
renotedBySomeUsers: "{n}人がリノートしました"
23592360
followedBySomeUsers: "{n}人にフォローされました"
23602361
flushNotification: "通知の履歴をリセットする"

packages/backend/src/core/entities/NoteEntityService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ export class NoteEntityService implements OnModuleInit {
333333
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
334334
renoteCount: note.renoteCount,
335335
repliesCount: note.repliesCount,
336+
reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0),
336337
reactions: this.reactionService.convertLegacyReactions(note.reactions),
337338
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
338339
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,

packages/backend/src/models/json-schema/note.ts

+4
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ export const packedNoteSchema = {
223223
}],
224224
},
225225
},
226+
reactionCount: {
227+
type: 'number',
228+
optional: false, nullable: false,
229+
},
226230
renoteCount: {
227231
type: 'number',
228232
optional: false, nullable: false,

packages/frontend/src/components/MkNote.vue

+17-8
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@ SPDX-License-Identifier: AGPL-3.0-only
9393
</div>
9494
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
9595
</div>
96-
<MkReactionsViewer :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
96+
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
9797
<template #more>
9898
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
9999
</template>
100100
</MkReactionsViewer>
101101
<footer :class="$style.footer">
102102
<button :class="$style.footerButton" class="_button" @click="reply()">
103103
<i class="ti ti-arrow-back-up"></i>
104-
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p>
104+
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
105105
</button>
106106
<button
107107
v-if="canRenote"
@@ -111,17 +111,17 @@ SPDX-License-Identifier: AGPL-3.0-only
111111
@mousedown="renote()"
112112
>
113113
<i class="ti ti-repeat"></i>
114-
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p>
114+
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p>
115115
</button>
116116
<button v-else :class="$style.footerButton" class="_button" disabled>
117117
<i class="ti ti-ban"></i>
118118
</button>
119-
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
120-
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
119+
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
120+
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
121+
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
122+
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
121123
<i v-else class="ti ti-plus"></i>
122-
</button>
123-
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
124-
<i class="ti ti-minus"></i>
124+
<p v-if="appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
125125
</button>
126126
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
127127
<i class="ti ti-paperclip"></i>
@@ -175,6 +175,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
175175
import { focusPrev, focusNext } from '@/scripts/focus.js';
176176
import { checkWordMute } from '@/scripts/check-word-mute.js';
177177
import { userPage } from '@/filters/user.js';
178+
import number from '@/filters/number.js';
178179
import * as os from '@/os.js';
179180
import * as sound from '@/scripts/sound.js';
180181
import { misskeyApi } from '@/scripts/misskey-api.js';
@@ -420,6 +421,14 @@ function undoReact(targetNote: Misskey.entities.Note): void {
420421
});
421422
}
422423

424+
function toggleReact() {
425+
if (appearNote.value.myReaction == null) {
426+
react();
427+
} else {
428+
undoReact(appearNote.value);
429+
}
430+
}
431+
423432
function onContextmenu(ev: MouseEvent): void {
424433
if (props.mock) {
425434
return;

packages/frontend/src/components/MkNoteDetailed.vue

+20-11
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,10 @@ SPDX-License-Identifier: AGPL-3.0-only
106106
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
107107
</MkA>
108108
</div>
109-
<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
109+
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/>
110110
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
111111
<i class="ti ti-arrow-back-up"></i>
112-
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p>
112+
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
113113
</button>
114114
<button
115115
v-if="canRenote"
@@ -119,17 +119,17 @@ SPDX-License-Identifier: AGPL-3.0-only
119119
@mousedown="renote()"
120120
>
121121
<i class="ti ti-repeat"></i>
122-
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p>
122+
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p>
123123
</button>
124124
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
125125
<i class="ti ti-ban"></i>
126126
</button>
127-
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
128-
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
127+
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
128+
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
129+
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
130+
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
129131
<i v-else class="ti ti-plus"></i>
130-
</button>
131-
<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
132-
<i class="ti ti-minus"></i>
132+
<p v-if="appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
133133
</button>
134134
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
135135
<i class="ti ti-paperclip"></i>
@@ -209,6 +209,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
209209
import { checkWordMute } from '@/scripts/check-word-mute.js';
210210
import { userPage } from '@/filters/user.js';
211211
import { notePage } from '@/filters/note.js';
212+
import number from '@/filters/number.js';
212213
import * as os from '@/os.js';
213214
import { misskeyApi } from '@/scripts/misskey-api.js';
214215
import * as sound from '@/scripts/sound.js';
@@ -401,14 +402,22 @@ function react(viaKeyboard = false): void {
401402
}
402403
}
403404

404-
function undoReact(note): void {
405-
const oldReaction = note.myReaction;
405+
function undoReact(targetNote: Misskey.entities.Note): void {
406+
const oldReaction = targetNote.myReaction;
406407
if (!oldReaction) return;
407408
misskeyApi('notes/reactions/delete', {
408-
noteId: note.id,
409+
noteId: targetNote.id,
409410
});
410411
}
411412

413+
function toggleReact() {
414+
if (appearNote.value.myReaction == null) {
415+
react();
416+
} else {
417+
undoReact(appearNote.value);
418+
}
419+
}
420+
412421
function onContextmenu(ev: MouseEvent): void {
413422
const isLink = (el: HTMLElement): boolean => {
414423
if (el.tagName === 'A') return true;

packages/frontend/src/components/MkNotification.vue

+17-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
88
<div :class="$style.head">
99
<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && notification.note" :class="$style.icon" :user="notification.note.user" link preview/>
1010
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
11+
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
1112
<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>
1213
<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>
1314
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
@@ -57,6 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
5758
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
5859
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
5960
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
61+
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: notification.reactions.length }) }}</span>
6062
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span>
6163
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
6264
<span v-else-if="notification.type === 'app'">{{ notification.header }}</span>
@@ -201,6 +203,7 @@ const rejectFollowRequest = () => {
201203
}
202204

203205
.icon_reactionGroup,
206+
.icon_reactionGroupHeart,
204207
.icon_renoteGroup {
205208
display: grid;
206209
align-items: center;
@@ -213,11 +216,15 @@ const rejectFollowRequest = () => {
213216
}
214217

215218
.icon_reactionGroup {
216-
background: #e99a0b;
219+
background: var(--eventReaction);
220+
}
221+
222+
.icon_reactionGroupHeart {
223+
background: var(--eventReactionHeart);
217224
}
218225

219226
.icon_renoteGroup {
220-
background: #36d298;
227+
background: var(--eventRenote);
221228
}
222229

223230
.icon_app {
@@ -246,49 +253,49 @@ const rejectFollowRequest = () => {
246253

247254
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest {
248255
padding: 3px;
249-
background: #36aed2;
256+
background: var(--eventFollow);
250257
pointer-events: none;
251258
}
252259

253260
.t_renote {
254261
padding: 3px;
255-
background: #36d298;
262+
background: var(--eventRenote);
256263
pointer-events: none;
257264
}
258265

259266
.t_quote {
260267
padding: 3px;
261-
background: #36d298;
268+
background: var(--eventRenote);
262269
pointer-events: none;
263270
}
264271

265272
.t_reply {
266273
padding: 3px;
267-
background: #007aff;
274+
background: var(--eventReply);
268275
pointer-events: none;
269276
}
270277

271278
.t_mention {
272279
padding: 3px;
273-
background: #88a6b7;
280+
background: var(--eventOther);
274281
pointer-events: none;
275282
}
276283

277284
.t_pollEnded {
278285
padding: 3px;
279-
background: #88a6b7;
286+
background: var(--eventOther);
280287
pointer-events: none;
281288
}
282289

283290
.t_achievementEarned {
284291
padding: 3px;
285-
background: #cb9a11;
292+
background: var(--eventAchievement);
286293
pointer-events: none;
287294
}
288295

289296
.t_roleAssigned {
290297
padding: 3px;
291-
background: #88a6b7;
298+
background: var(--eventOther);
292299
pointer-events: none;
293300
}
294301

packages/frontend/src/components/MkTutorialDialog.Note.vue

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
6363
reactionAcceptance: null,
6464
renoteCount: 0,
6565
repliesCount: 1,
66+
reactionCount: 0,
6667
reactions: {},
6768
reactionEmojis: {},
6869
fileIds: [],

packages/frontend/src/components/MkTutorialDialog.PostNote.vue

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
6868
reactionAcceptance: null,
6969
renoteCount: 0,
7070
repliesCount: 1,
71+
reactionCount: 0,
7172
reactions: {},
7273
reactionEmojis: {},
7374
fileIds: [],

packages/frontend/src/components/MkTutorialDialog.Sensitive.vue

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
5858
reactionAcceptance: null,
5959
renoteCount: 0,
6060
repliesCount: 1,
61+
reactionCount: 0,
6162
reactions: {},
6263
reactionEmojis: {},
6364
fileIds: ['0000000002'],

packages/frontend/src/scripts/use-note-capture.ts

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export function useNoteCapture(props: {
3535
const currentCount = (note.value.reactions || {})[reaction] || 0;
3636

3737
note.value.reactions[reaction] = currentCount + 1;
38+
note.value.reactionCount += 1;
3839

3940
if ($i && (body.userId === $i.id)) {
4041
note.value.myReaction = reaction;
@@ -49,6 +50,7 @@ export function useNoteCapture(props: {
4950
const currentCount = (note.value.reactions || {})[reaction] || 0;
5051

5152
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
53+
note.value.reactionCount = Math.max(0, note.value.reactionCount - 1);
5254
if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction];
5355

5456
if ($i && (body.userId === $i.id)) {

packages/frontend/src/style.scss

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
}
2323

2424
//--ad: rgb(255 169 0 / 10%);
25+
--eventFollow: #36aed2;
26+
--eventRenote: #36d298;
27+
--eventReply: #007aff;
28+
--eventReactionHeart: #dd2e44;
29+
--eventReaction: #e99a0b;
30+
--eventAchievement: #cb9a11;
31+
--eventOther: #88a6b7;
2532
}
2633

2734
::selection {

packages/misskey-js/src/autogen/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3987,6 +3987,7 @@ export type components = {
39873987
reactions: {
39883988
[key: string]: number;
39893989
};
3990+
reactionCount: number;
39903991
renoteCount: number;
39913992
repliesCount: number;
39923993
uri?: string;

0 commit comments

Comments
 (0)