wip
This commit is contained in:
parent
6b7806a3ae
commit
7246f6529f
|
@ -210,7 +210,7 @@ import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
|
||||||
import { $i } from '@/i.js';
|
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 { useNoteCapture } from '@/use/use-note-capture.js';
|
import { noteEvents, useNoteCapture } from '@/use/use-note-capture.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { useTooltip } from '@/use/use-tooltip.js';
|
import { useTooltip } from '@/use/use-tooltip.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
|
@ -382,6 +382,11 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
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', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: '❤️',
|
reaction: '❤️',
|
||||||
|
}).then(() => {
|
||||||
|
noteEvents.emit(`reacted:${appearNote.value.id}`, {
|
||||||
|
userId: $i!.id,
|
||||||
|
reaction: '❤️',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const el = reactButton.value;
|
const el = reactButton.value;
|
||||||
if (el && prefer.s.animation) {
|
if (el && prefer.s.animation) {
|
||||||
|
@ -513,7 +523,10 @@ function react(): void {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
}).then(() => {
|
}).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)) {
|
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
|
||||||
|
|
|
@ -242,7 +242,7 @@ import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
|
||||||
import { $i } from '@/i.js';
|
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 { useNoteCapture } from '@/use/use-note-capture.js';
|
import { noteEvents, useNoteCapture } from '@/use/use-note-capture.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { useTooltip } from '@/use/use-tooltip.js';
|
import { useTooltip } from '@/use/use-tooltip.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
|
@ -343,6 +343,11 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
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', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: '❤️',
|
reaction: '❤️',
|
||||||
|
}).then(() => {
|
||||||
|
noteEvents.emit(`reacted:${appearNote.value.id}`, {
|
||||||
|
userId: $i!.id,
|
||||||
|
reaction: '❤️',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const el = reactButton.value;
|
const el = reactButton.value;
|
||||||
if (el && prefer.s.animation) {
|
if (el && prefer.s.animation) {
|
||||||
|
@ -472,6 +482,11 @@ function react(): void {
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
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)) {
|
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
|
||||||
claimAchievement('reactWithoutRead');
|
claimAchievement('reactWithoutRead');
|
||||||
|
|
|
@ -105,11 +105,7 @@ function onNotification(notification) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
return new Promise<void>((res) => {
|
return paginator.reload();
|
||||||
paginator.reload().then(() => {
|
|
||||||
res();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection: Misskey.ChannelConnection<Misskey.Channels['main']> | null = null;
|
let connection: Misskey.ChannelConnection<Misskey.Channels['main']> | null = null;
|
||||||
|
|
|
@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
|
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
|
||||||
:enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
|
:enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
|
||||||
:leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
|
:leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
|
||||||
|
:css="prefer.s.animation"
|
||||||
mode="out-in"
|
mode="out-in"
|
||||||
>
|
>
|
||||||
<MkLoading v-if="paginator.fetching.value"/>
|
<MkLoading v-if="paginator.fetching.value"/>
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { checkReactionPermissions } from '@/utility/check-reaction-permissions.j
|
||||||
import { customEmojisMap } from '@/custom-emojis.js';
|
import { customEmojisMap } from '@/custom-emojis.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
import { noteEvents } from '@/use/use-note-capture.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reaction: string;
|
reaction: string;
|
||||||
|
@ -83,10 +84,21 @@ async function toggleReaction() {
|
||||||
misskeyApi('notes/reactions/delete', {
|
misskeyApi('notes/reactions/delete', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
noteEvents.emit(`unreacted:${props.note.id}`, {
|
||||||
|
userId: $i!.id,
|
||||||
|
reaction: props.reaction,
|
||||||
|
emoji: emoji.value,
|
||||||
|
});
|
||||||
if (oldReaction !== props.reaction) {
|
if (oldReaction !== props.reaction) {
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
reaction: props.reaction,
|
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', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
reaction: props.reaction,
|
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)) {
|
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
|
||||||
claimAchievement('reactWithoutRead');
|
claimAchievement('reactWithoutRead');
|
||||||
|
|
|
@ -12,11 +12,11 @@ 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';
|
||||||
|
|
||||||
const noteEvents = new EventEmitter<{
|
export const noteEvents = new EventEmitter<{
|
||||||
reacted: Misskey.entities.Note;
|
[`reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void;
|
||||||
unreacted: Misskey.entities.Note;
|
[`unreacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void;
|
||||||
pollVoted: Misskey.entities.Note;
|
[`pollVoted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; choice: string; }) => void;
|
||||||
deleted: Misskey.entities.Note;
|
[`deleted:${string}`]: () => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const fetchEvent = new EventEmitter<{
|
const fetchEvent = new EventEmitter<{
|
||||||
|
@ -55,10 +55,6 @@ function pseudoNoteCapture(props: {
|
||||||
const note = props.note;
|
const note = props.note;
|
||||||
const pureNote = props.pureNote;
|
const pureNote = props.pureNote;
|
||||||
|
|
||||||
function onReacted(): void {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFetched(data: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>): void {
|
function onFetched(data: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>): void {
|
||||||
note.value.reactions = data.reactions;
|
note.value.reactions = data.reactions;
|
||||||
note.value.reactionCount = Object.values(data.reactions).reduce((a, b) => a + b, 0);
|
note.value.reactionCount = Object.values(data.reactions).reduce((a, b) => a + b, 0);
|
||||||
|
@ -100,58 +96,33 @@ function realtimeNoteCapture(props: {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'reacted': {
|
case 'reacted': {
|
||||||
const reaction = body.reaction;
|
noteEvents.emit(`reacted:${id}`, {
|
||||||
|
userId: body.userId,
|
||||||
if (body.emoji && !(body.emoji.name in note.value.reactionEmojis)) {
|
reaction: body.reaction,
|
||||||
note.value.reactionEmojis[body.emoji.name] = body.emoji.url;
|
emoji: body.emoji,
|
||||||
}
|
});
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'unreacted': {
|
case 'unreacted': {
|
||||||
const reaction = body.reaction;
|
noteEvents.emit(`unreacted:${id}`, {
|
||||||
|
userId: body.userId,
|
||||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
reaction: body.reaction,
|
||||||
const currentCount = (note.value.reactions || {})[reaction] || 0;
|
emoji: body.emoji,
|
||||||
|
});
|
||||||
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;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'pollVoted': {
|
case 'pollVoted': {
|
||||||
const choice = body.choice;
|
noteEvents.emit(`pollVoted:${id}`, {
|
||||||
|
userId: body.userId,
|
||||||
const choices = [...note.value.poll.choices];
|
choice: body.choice,
|
||||||
choices[choice] = {
|
});
|
||||||
...choices[choice],
|
|
||||||
votes: choices[choice].votes + 1,
|
|
||||||
...($i && (body.userId === $i.id) ? {
|
|
||||||
isVoted: true,
|
|
||||||
} : {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
note.value.poll.choices = choices;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'deleted': {
|
case 'deleted': {
|
||||||
props.isDeletedRef.value = true;
|
noteEvents.emit(`deleted:${id}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,6 +166,80 @@ export function useNoteCapture(props: {
|
||||||
pureNote: Ref<Misskey.entities.Note>;
|
pureNote: Ref<Misskey.entities.Note>;
|
||||||
isDeletedRef: Ref<boolean>;
|
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) {
|
if ($i && store.s.realtimeMode) {
|
||||||
realtimeNoteCapture(props);
|
realtimeNoteCapture(props);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue