This commit is contained in:
syuilo 2025-04-29 21:21:11 +09:00
parent 6b7806a3ae
commit 7246f6529f
6 changed files with 145 additions and 57 deletions

View File

@ -210,7 +210,7 @@ import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
import { useNoteCapture } from '@/use/use-note-capture.js';
import { noteEvents, useNoteCapture } from '@/use/use-note-capture.js';
import { deepClone } from '@/utility/clone.js';
import { useTooltip } from '@/use/use-tooltip.js';
import { claimAchievement } from '@/utility/achievements.js';
@ -382,6 +382,11 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
}).then(() => {
noteEvents.emit(`reacted:${appearNote.value.id}`, {
userId: $i!.id,
reaction: reaction,
});
});
});
@ -480,6 +485,11 @@ function react(): void {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: '❤️',
}).then(() => {
noteEvents.emit(`reacted:${appearNote.value.id}`, {
userId: $i!.id,
reaction: '❤️',
});
});
const el = reactButton.value;
if (el && prefer.s.animation) {
@ -513,7 +523,10 @@ function react(): void {
noteId: appearNote.value.id,
reaction: reaction,
}).then(() => {
// then()
noteEvents.emit(`reacted:${appearNote.value.id}`, {
userId: $i!.id,
reaction: reaction,
});
});
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {

View File

@ -242,7 +242,7 @@ import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
import { useNoteCapture } from '@/use/use-note-capture.js';
import { noteEvents, useNoteCapture } from '@/use/use-note-capture.js';
import { deepClone } from '@/utility/clone.js';
import { useTooltip } from '@/use/use-tooltip.js';
import { claimAchievement } from '@/utility/achievements.js';
@ -343,6 +343,11 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
}).then(() => {
noteEvents.emit(`reacted:${appearNote.value.id}`, {
userId: $i!.id,
reaction: reaction,
});
});
});
@ -445,6 +450,11 @@ function react(): void {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: '❤️',
}).then(() => {
noteEvents.emit(`reacted:${appearNote.value.id}`, {
userId: $i!.id,
reaction: '❤️',
});
});
const el = reactButton.value;
if (el && prefer.s.animation) {
@ -472,6 +482,11 @@ function react(): void {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
}).then(() => {
noteEvents.emit(`reacted:${appearNote.value.id}`, {
userId: $i!.id,
reaction: reaction,
});
});
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');

View File

@ -105,11 +105,7 @@ function onNotification(notification) {
}
function reload() {
return new Promise<void>((res) => {
paginator.reload().then(() => {
res();
});
});
return paginator.reload();
}
let connection: Misskey.ChannelConnection<Misskey.Channels['main']> | null = null;

View File

@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
:css="prefer.s.animation"
mode="out-in"
>
<MkLoading v-if="paginator.fetching.value"/>

View File

@ -36,6 +36,7 @@ import { checkReactionPermissions } from '@/utility/check-reaction-permissions.j
import { customEmojisMap } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
import { noteEvents } from '@/use/use-note-capture.js';
const props = defineProps<{
reaction: string;
@ -83,10 +84,21 @@ async function toggleReaction() {
misskeyApi('notes/reactions/delete', {
noteId: props.note.id,
}).then(() => {
noteEvents.emit(`unreacted:${props.note.id}`, {
userId: $i!.id,
reaction: props.reaction,
emoji: emoji.value,
});
if (oldReaction !== props.reaction) {
misskeyApi('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
}).then(() => {
noteEvents.emit(`reacted:${props.note.id}`, {
userId: $i!.id,
reaction: props.reaction,
emoji: emoji.value,
});
});
}
});
@ -110,6 +122,12 @@ async function toggleReaction() {
misskeyApi('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
}).then(() => {
noteEvents.emit(`reacted:${props.note.id}`, {
userId: $i!.id,
reaction: props.reaction,
emoji: emoji.value,
});
});
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');

View File

@ -12,11 +12,11 @@ import { $i } from '@/i.js';
import { store } from '@/store.js';
import { misskeyApi } from '@/utility/misskey-api.js';
const noteEvents = new EventEmitter<{
reacted: Misskey.entities.Note;
unreacted: Misskey.entities.Note;
pollVoted: Misskey.entities.Note;
deleted: Misskey.entities.Note;
export const noteEvents = new EventEmitter<{
[`reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void;
[`unreacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void;
[`pollVoted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; choice: string; }) => void;
[`deleted:${string}`]: () => void;
}>();
const fetchEvent = new EventEmitter<{
@ -55,10 +55,6 @@ function pseudoNoteCapture(props: {
const note = props.note;
const pureNote = props.pureNote;
function onReacted(): void {
}
function onFetched(data: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>): void {
note.value.reactions = data.reactions;
note.value.reactionCount = Object.values(data.reactions).reduce((a, b) => a + b, 0);
@ -100,58 +96,33 @@ function realtimeNoteCapture(props: {
switch (type) {
case 'reacted': {
const reaction = body.reaction;
if (body.emoji && !(body.emoji.name in note.value.reactionEmojis)) {
note.value.reactionEmojis[body.emoji.name] = body.emoji.url;
}
// TODO: reactionsプロパティがない場合ってあったっけ なければ || {} は消せる
const currentCount = (note.value.reactions || {})[reaction] || 0;
note.value.reactions[reaction] = currentCount + 1;
note.value.reactionCount += 1;
if ($i && (body.userId === $i.id)) {
note.value.myReaction = reaction;
}
noteEvents.emit(`reacted:${id}`, {
userId: body.userId,
reaction: body.reaction,
emoji: body.emoji,
});
break;
}
case 'unreacted': {
const reaction = body.reaction;
// TODO: reactionsプロパティがない場合ってあったっけ なければ || {} は消せる
const currentCount = (note.value.reactions || {})[reaction] || 0;
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
note.value.reactionCount = Math.max(0, note.value.reactionCount - 1);
if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction];
if ($i && (body.userId === $i.id)) {
note.value.myReaction = null;
}
noteEvents.emit(`unreacted:${id}`, {
userId: body.userId,
reaction: body.reaction,
emoji: body.emoji,
});
break;
}
case 'pollVoted': {
const choice = body.choice;
const choices = [...note.value.poll.choices];
choices[choice] = {
...choices[choice],
votes: choices[choice].votes + 1,
...($i && (body.userId === $i.id) ? {
isVoted: true,
} : {}),
};
note.value.poll.choices = choices;
noteEvents.emit(`pollVoted:${id}`, {
userId: body.userId,
choice: body.choice,
});
break;
}
case 'deleted': {
props.isDeletedRef.value = true;
noteEvents.emit(`deleted:${id}`);
break;
}
}
@ -195,6 +166,80 @@ export function useNoteCapture(props: {
pureNote: Ref<Misskey.entities.Note>;
isDeletedRef: Ref<boolean>;
}) {
const note = props.note;
noteEvents.on(`reacted:${note.value.id}`, onReacted);
noteEvents.on(`unreacted:${note.value.id}`, onUnreacted);
noteEvents.on(`pollVoted:${note.value.id}`, onPollVoted);
noteEvents.on(`deleted:${note.value.id}`, onDeleted);
let latestReactedKey: string | null = null;
let latestUnreactedKey: string | null = null;
let latestPollVotedKey: string | null = null;
function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void {
console.log('reacted', ctx);
const newReactedKey = `${ctx.userId}:${ctx.reaction}`;
if (newReactedKey === latestReactedKey) return;
latestReactedKey = newReactedKey;
if (ctx.emoji && !(ctx.emoji.name in note.value.reactionEmojis)) {
note.value.reactionEmojis[ctx.emoji.name] = ctx.emoji.url;
}
const currentCount = note.value.reactions[ctx.reaction] || 0;
note.value.reactions[ctx.reaction] = currentCount + 1;
note.value.reactionCount += 1;
if ($i && (ctx.userId === $i.id)) {
note.value.myReaction = ctx.reaction;
}
}
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 currentCount = note.value.reactions[ctx.reaction] || 0;
note.value.reactions[ctx.reaction] = Math.max(0, currentCount - 1);
note.value.reactionCount = Math.max(0, note.value.reactionCount - 1);
if (note.value.reactions[ctx.reaction] === 0) delete note.value.reactions[ctx.reaction];
if ($i && (ctx.userId === $i.id)) {
note.value.myReaction = null;
}
}
function onPollVoted(ctx: { userId: Misskey.entities.User['id']; choice: string; }): void {
const newPollVotedKey = `${ctx.userId}:${ctx.choice}`;
if (newPollVotedKey === latestPollVotedKey) return;
latestPollVotedKey = newPollVotedKey;
const choices = [...note.value.poll.choices];
choices[ctx.choice] = {
...choices[ctx.choice],
votes: choices[ctx.choice].votes + 1,
...($i && (ctx.userId === $i.id) ? {
isVoted: true,
} : {}),
};
note.value.poll.choices = choices;
}
function onDeleted(): void {
props.isDeletedRef.value = true;
}
onUnmounted(() => {
noteEvents.off(`reacted:${note.value.id}`, onReacted);
noteEvents.off(`unreacted:${note.value.id}`, onUnreacted);
noteEvents.off(`pollVoted:${note.value.id}`, onPollVoted);
noteEvents.off(`deleted:${note.value.id}`, onDeleted);
});
if ($i && store.s.realtimeMode) {
realtimeNoteCapture(props);
} else {