fix(frontend): カスタム絵文字のリアクションが二重で表示されることがある問題を修正
This commit is contained in:
parent
fe1b2b00f5
commit
c1e894fb96
|
|
@ -228,6 +228,7 @@ import { $i } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
|
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
|
||||||
import { noteEvents, useNoteCapture } from '@/composables/use-note-capture.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 { deepClone } from '@/utility/clone.js';
|
||||||
import { useTooltip } from '@/composables/use-tooltip.js';
|
import { useTooltip } from '@/composables/use-tooltip.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
|
|
@ -283,12 +284,12 @@ if (noteViewInterruptors.length > 0) {
|
||||||
|
|
||||||
const isRenote = Misskey.note.isPureRenote(note);
|
const isRenote = Misskey.note.isPureRenote(note);
|
||||||
const appearNote = getAppearNote(note);
|
const appearNote = getAppearNote(note);
|
||||||
const $appearNote = reactive({
|
const $appearNote = reactive<ReactiveNoteData>({
|
||||||
reactions: appearNote.reactions,
|
reactions: appearNote.reactions,
|
||||||
reactionCount: appearNote.reactionCount,
|
reactionCount: appearNote.reactionCount,
|
||||||
reactionEmojis: appearNote.reactionEmojis,
|
reactionEmojis: appearNote.reactionEmojis,
|
||||||
myReaction: appearNote.myReaction,
|
myReaction: appearNote.myReaction,
|
||||||
pollChoices: appearNote.poll?.choices,
|
pollChoices: appearNote.poll?.choices ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ import { $i } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
|
import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
|
||||||
import { noteEvents, useNoteCapture } from '@/composables/use-note-capture.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 { deepClone } from '@/utility/clone.js';
|
||||||
import { useTooltip } from '@/composables/use-tooltip.js';
|
import { useTooltip } from '@/composables/use-tooltip.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
|
|
@ -304,12 +305,12 @@ if (noteViewInterruptors.length > 0) {
|
||||||
|
|
||||||
const isRenote = Misskey.note.isPureRenote(note);
|
const isRenote = Misskey.note.isPureRenote(note);
|
||||||
const appearNote = getAppearNote(note);
|
const appearNote = getAppearNote(note);
|
||||||
const $appearNote = reactive({
|
const $appearNote = reactive<ReactiveNoteData>({
|
||||||
reactions: appearNote.reactions,
|
reactions: appearNote.reactions,
|
||||||
reactionCount: appearNote.reactionCount,
|
reactionCount: appearNote.reactionCount,
|
||||||
reactionEmojis: appearNote.reactionEmojis,
|
reactionEmojis: appearNote.reactionEmojis,
|
||||||
myReaction: appearNote.myReaction,
|
myReaction: appearNote.myReaction,
|
||||||
pollChoices: appearNote.poll?.choices,
|
pollChoices: appearNote.poll?.choices ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@
|
||||||
import { onUnmounted } from 'vue';
|
import { onUnmounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import type { Reactive, Ref } from 'vue';
|
import type { Reactive } from 'vue';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
|
import { name } from 'happy-dom/lib/PropertySymbol.js';
|
||||||
|
|
||||||
export const noteEvents = new EventEmitter<{
|
export const noteEvents = new EventEmitter<{
|
||||||
[ev: `reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void;
|
[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'];
|
reactions: Misskey.entities.Note['reactions'];
|
||||||
reactionCount: Misskey.entities.Note['reactionCount'];
|
reactionCount: Misskey.entities.Note['reactionCount'];
|
||||||
reactionEmojis: Misskey.entities.Note['reactionEmojis'];
|
reactionEmojis: Misskey.entities.Note['reactionEmojis'];
|
||||||
myReaction: Misskey.entities.Note['myReaction'];
|
myReaction: Misskey.entities.Note['myReaction'];
|
||||||
pollChoices: NonNullable<Misskey.entities.Note['poll']>['choices'];
|
pollChoices: NonNullable<Misskey.entities.Note['poll']>['choices'];
|
||||||
}>;
|
};
|
||||||
|
|
||||||
export function useNoteCapture(props: {
|
export function useNoteCapture(props: {
|
||||||
note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>;
|
note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>;
|
||||||
parentNote: Misskey.entities.Note | null;
|
parentNote: Misskey.entities.Note | null;
|
||||||
$note: ReactiveNoteData;
|
$note: Reactive<ReactiveNoteData>;
|
||||||
}): {
|
}): {
|
||||||
subscribe: () => void;
|
subscribe: () => void;
|
||||||
} {
|
} {
|
||||||
const { note, parentNote, $note } = props;
|
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(`reacted:${note.id}`, onReacted);
|
||||||
noteEvents.on(`unreacted:${note.id}`, onUnreacted);
|
noteEvents.on(`unreacted:${note.id}`, onUnreacted);
|
||||||
noteEvents.on(`pollVoted:${note.id}`, onPollVoted);
|
noteEvents.on(`pollVoted:${note.id}`, onPollVoted);
|
||||||
|
|
@ -205,7 +219,8 @@ export function useNoteCapture(props: {
|
||||||
let latestPollVotedKey: string | null = null;
|
let latestPollVotedKey: string | null = null;
|
||||||
|
|
||||||
function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void {
|
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;
|
if (newReactedKey === latestReactedKey) return;
|
||||||
latestReactedKey = newReactedKey;
|
latestReactedKey = newReactedKey;
|
||||||
|
|
||||||
|
|
@ -213,26 +228,27 @@ export function useNoteCapture(props: {
|
||||||
$note.reactionEmojis[ctx.emoji.name] = ctx.emoji.url;
|
$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;
|
$note.reactionCount += 1;
|
||||||
|
|
||||||
if ($i && (ctx.userId === $i.id)) {
|
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 {
|
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;
|
if (newUnreactedKey === latestUnreactedKey) return;
|
||||||
latestUnreactedKey = newUnreactedKey;
|
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);
|
$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)) {
|
if ($i && (ctx.userId === $i.id)) {
|
||||||
$note.myReaction = null;
|
$note.myReaction = null;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue