Compare commits

...

4 Commits

Author SHA1 Message Date
kakkokari-gtyih aaf93607bc fix: unused import 2025-05-24 17:30:02 +09:00
kakkokari-gtyih e539bde144 fix: remove unused console log 2025-05-24 17:26:53 +09:00
kakkokari-gtyih 833c453994 fix: improve event locking mechanism 2025-05-24 17:26:36 +09:00
kakkokari-gtyih c1e894fb96 fix(frontend): カスタム絵文字のリアクションが二重で表示されることがある問題を修正 2025-05-24 17:15:21 +09:00
3 changed files with 39 additions and 22 deletions
+3 -2
View File
@@ -228,6 +228,7 @@ import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
import { noteEvents, useNoteCapture } from '@/composables/use-note-capture.js';
import type { ReactiveNoteData } from '@/composables/use-note-capture.js';
import { deepClone } from '@/utility/clone.js';
import { useTooltip } from '@/composables/use-tooltip.js';
import { claimAchievement } from '@/utility/achievements.js';
@@ -283,12 +284,12 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note);
const appearNote = getAppearNote(note);
const $appearNote = reactive({
const $appearNote = reactive<ReactiveNoteData>({
reactions: appearNote.reactions,
reactionCount: appearNote.reactionCount,
reactionEmojis: appearNote.reactionEmojis,
myReaction: appearNote.myReaction,
pollChoices: appearNote.poll?.choices,
pollChoices: appearNote.poll?.choices ?? [],
});
const rootEl = useTemplateRef('rootEl');
@@ -259,6 +259,7 @@ import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
import { noteEvents, useNoteCapture } from '@/composables/use-note-capture.js';
import type { ReactiveNoteData } from '@/composables/use-note-capture.js';
import { deepClone } from '@/utility/clone.js';
import { useTooltip } from '@/composables/use-tooltip.js';
import { claimAchievement } from '@/utility/achievements.js';
@@ -304,12 +305,12 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note);
const appearNote = getAppearNote(note);
const $appearNote = reactive({
const $appearNote = reactive<ReactiveNoteData>({
reactions: appearNote.reactions,
reactionCount: appearNote.reactionCount,
reactionEmojis: appearNote.reactionEmojis,
myReaction: appearNote.myReaction,
pollChoices: appearNote.poll?.choices,
pollChoices: appearNote.poll?.choices ?? [],
});
const rootEl = useTemplateRef('rootEl');
@@ -6,7 +6,7 @@
import { onUnmounted } 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,75 @@ 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'>;
parentNote: Misskey.entities.Note | null;
$note: ReactiveNoteData;
$note: Reactive<ReactiveNoteData>;
}): {
subscribe: () => void;
} {
const { note, parentNote, $note } = props;
// Normalize reactions
if (Object.keys($note.reactions).length > 0) {
$note.reactions = Object.entries($note.reactions).reduce((acc, [name, count]) => {
const normalizedName = name.replace(/^:(\w+):$/, ':$1@.:');
if (acc[normalizedName] == null) {
acc[normalizedName] = count;
} else {
acc[normalizedName] += count;
}
return acc;
}, {} as Misskey.entities.Note['reactions']);
}
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;