summaryrefslogtreecommitdiff
path: root/packages/frontend/src/composables/use-note-capture.ts
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-05-24 18:31:55 +0900
committerGitHub <noreply@github.com>2025-05-24 18:31:55 +0900
commit02041344bdc2ac39c67d9b01e88d65c983059dcb (patch)
tree57bb18c5a3d06925d9ad965d9e9efb566bc4a7d7 /packages/frontend/src/composables/use-note-capture.ts
parentBump version to 2025.5.1-beta.1 (diff)
downloadmisskey-02041344bdc2ac39c67d9b01e88d65c983059dcb.tar.gz
misskey-02041344bdc2ac39c67d9b01e88d65c983059dcb.tar.bz2
misskey-02041344bdc2ac39c67d9b01e88d65c983059dcb.zip
fix(frontend); カスタム絵文字のリアクションが二重で表示されることがある問題を修正 (#16092)
* fix(frontend): カスタム絵文字のリアクションが二重で表示されることがある問題を修正 * fix: improve event locking mechanism * fix: remove unused console log * fix: unused import * fix: その場で書き換えることで再レンダリングを最小限に抑える(かも) * refactor: reactive note data を composable内で生成するように
Diffstat (limited to '')
-rw-r--r--packages/frontend/src/composables/use-note-capture.ts79
1 files changed, 56 insertions, 23 deletions
diff --git a/packages/frontend/src/composables/use-note-capture.ts b/packages/frontend/src/composables/use-note-capture.ts
index dd00c2b66e..a8f4a5a2a9 100644
--- a/packages/frontend/src/composables/use-note-capture.ts
+++ b/packages/frontend/src/composables/use-note-capture.ts
@@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { onUnmounted } from 'vue';
+import { onUnmounted, reactive } from 'vue';
import * as Misskey from 'misskey-js';
import { EventEmitter } from 'eventemitter3';
-import type { Reactive, Ref } from 'vue';
+import type { Reactive } from 'vue';
import { useStream } from '@/stream.js';
import { $i } from '@/i.js';
import { store } from '@/store.js';
@@ -179,60 +179,80 @@ function realtimeSubscribe(props: {
});
}
-type ReactiveNoteData = Reactive<{
+export type ReactiveNoteData = {
reactions: Misskey.entities.Note['reactions'];
reactionCount: Misskey.entities.Note['reactionCount'];
reactionEmojis: Misskey.entities.Note['reactionEmojis'];
myReaction: Misskey.entities.Note['myReaction'];
pollChoices: NonNullable<Misskey.entities.Note['poll']>['choices'];
-}>;
+};
export function useNoteCapture(props: {
- note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>;
+ note: Misskey.entities.Note;
parentNote: Misskey.entities.Note | null;
- $note: ReactiveNoteData;
+ mock?: boolean;
}): {
+ $note: Reactive<ReactiveNoteData>;
subscribe: () => void;
} {
- const { note, parentNote, $note } = props;
+ const { note, parentNote, mock } = props;
+
+ const $note = reactive<ReactiveNoteData>({
+ reactions: Object.entries(note.reactions).reduce((acc, [name, count]) => {
+ // Normalize reactions
+ const normalizedName = name.replace(/^:(\w+):$/, ':$1@.:');
+ if (acc[normalizedName] == null) {
+ acc[normalizedName] = count;
+ } else {
+ acc[normalizedName] += count;
+ }
+ return acc;
+ }, {} as Misskey.entities.Note['reactions']),
+ reactionCount: note.reactionCount,
+ reactionEmojis: note.reactionEmojis,
+ myReaction: note.myReaction,
+ pollChoices: note.poll?.choices ?? [],
+ });
noteEvents.on(`reacted:${note.id}`, onReacted);
noteEvents.on(`unreacted:${note.id}`, onUnreacted);
noteEvents.on(`pollVoted:${note.id}`, onPollVoted);
- let latestReactedKey: string | null = null;
- let latestUnreactedKey: string | null = null;
+ // 操作がダブっていないかどうかを簡易的に記録するためのMap
+ const reactionUserMap = new Map<Misskey.entities.User['id'], string>();
let latestPollVotedKey: string | null = null;
function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void {
- const newReactedKey = `${ctx.userId}:${ctx.reaction}`;
- if (newReactedKey === latestReactedKey) return;
- latestReactedKey = newReactedKey;
+ const normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:');
+
+ if (reactionUserMap.has(ctx.userId) && reactionUserMap.get(ctx.userId) === normalizedName) return;
+ reactionUserMap.set(ctx.userId, normalizedName);
if (ctx.emoji && !(ctx.emoji.name in $note.reactionEmojis)) {
$note.reactionEmojis[ctx.emoji.name] = ctx.emoji.url;
}
- const currentCount = $note.reactions[ctx.reaction] || 0;
+ const currentCount = $note.reactions[normalizedName] || 0;
- $note.reactions[ctx.reaction] = currentCount + 1;
+ $note.reactions[normalizedName] = currentCount + 1;
$note.reactionCount += 1;
if ($i && (ctx.userId === $i.id)) {
- $note.myReaction = ctx.reaction;
+ $note.myReaction = normalizedName;
}
}
function onUnreacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void {
- const newUnreactedKey = `${ctx.userId}:${ctx.reaction}`;
- if (newUnreactedKey === latestUnreactedKey) return;
- latestUnreactedKey = newUnreactedKey;
+ const normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:');
- const currentCount = $note.reactions[ctx.reaction] || 0;
+ if (!reactionUserMap.has(ctx.userId)) return;
+ reactionUserMap.delete(ctx.userId);
- $note.reactions[ctx.reaction] = Math.max(0, currentCount - 1);
+ const currentCount = $note.reactions[normalizedName] || 0;
+
+ $note.reactions[normalizedName] = Math.max(0, currentCount - 1);
$note.reactionCount = Math.max(0, $note.reactionCount - 1);
- if ($note.reactions[ctx.reaction] === 0) delete $note.reactions[ctx.reaction];
+ if ($note.reactions[normalizedName] === 0) delete $note.reactions[normalizedName];
if ($i && (ctx.userId === $i.id)) {
$note.myReaction = null;
@@ -257,10 +277,20 @@ export function useNoteCapture(props: {
}
function subscribe() {
+ if (mock) {
+ // モックモードでは購読しない
+ return;
+ }
+
if ($i && store.s.realtimeMode) {
- realtimeSubscribe(props);
+ realtimeSubscribe({
+ note,
+ });
} else {
- pollingSubscribe(props);
+ pollingSubscribe({
+ note,
+ $note,
+ });
}
}
@@ -277,6 +307,7 @@ export function useNoteCapture(props: {
if ((Date.now() - new Date(note.createdAt).getTime()) > 1000 * 60 * 5) { // 5min
// リノートで表示されているノートでもないし、投稿からある程度経過しているので自動で購読しない
return {
+ $note,
subscribe: () => {
subscribe();
},
@@ -286,6 +317,7 @@ export function useNoteCapture(props: {
if ((Date.now() - new Date(parentNote.createdAt).getTime()) > 1000 * 60 * 5) { // 5min
// リノートで表示されているノートだが、リノートされてからある程度経過しているので自動で購読しない
return {
+ $note,
subscribe: () => {
subscribe();
},
@@ -296,6 +328,7 @@ export function useNoteCapture(props: {
subscribe();
return {
+ $note,
subscribe: () => {
// すでに購読しているので何もしない
},