diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index ee8a470b0e..98491a8992 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -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({ reactions: appearNote.reactions, reactionCount: appearNote.reactionCount, reactionEmojis: appearNote.reactionEmojis, myReaction: appearNote.myReaction, - pollChoices: appearNote.poll?.choices, + pollChoices: appearNote.poll?.choices ?? [], }); const rootEl = useTemplateRef('rootEl'); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 767f4c88ce..9f92402fd1 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -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({ reactions: appearNote.reactions, reactionCount: appearNote.reactionCount, reactionEmojis: appearNote.reactionEmojis, myReaction: appearNote.myReaction, - pollChoices: appearNote.poll?.choices, + pollChoices: appearNote.poll?.choices ?? [], }); const rootEl = useTemplateRef('rootEl'); diff --git a/packages/frontend/src/composables/use-note-capture.ts b/packages/frontend/src/composables/use-note-capture.ts index dd00c2b66e..7d1e75fc04 100644 --- a/packages/frontend/src/composables/use-note-capture.ts +++ b/packages/frontend/src/composables/use-note-capture.ts @@ -6,13 +6,14 @@ 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'; import { misskeyApi } from '@/utility/misskey-api.js'; import { prefer } from '@/preferences.js'; import { globalEvents } from '@/events.js'; +import { name } from 'happy-dom/lib/PropertySymbol.js'; export const noteEvents = new EventEmitter<{ [ev: `reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void; @@ -179,23 +180,36 @@ 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['choices']; -}>; +}; export function useNoteCapture(props: { note: Pick; parentNote: Misskey.entities.Note | null; - $note: ReactiveNoteData; + $note: Reactive; }): { 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); @@ -205,7 +219,8 @@ export function useNoteCapture(props: { 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}`; + const normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:'); + const newReactedKey = `${ctx.userId}:${normalizedName}`; if (newReactedKey === latestReactedKey) return; latestReactedKey = newReactedKey; @@ -213,26 +228,27 @@ export function useNoteCapture(props: { $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}`; + const normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:'); + const newUnreactedKey = `${ctx.userId}:${normalizedName}`; if (newUnreactedKey === latestUnreactedKey) return; latestUnreactedKey = newUnreactedKey; - const currentCount = $note.reactions[ctx.reaction] || 0; + const currentCount = $note.reactions[normalizedName] || 0; - $note.reactions[ctx.reaction] = Math.max(0, currentCount - 1); + $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;