This commit is contained in:
syuilo 2025-05-01 21:46:16 +09:00
parent 66c3666d0c
commit 5ac2116449
3 changed files with 70 additions and 76 deletions

View File

@ -92,7 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:noteId="appearNote.id"
:multiple="appearNote.poll.multiple"
:expiresAt="appearNote.poll.expiresAt"
:choices="pollChoices"
:choices="$appearNote.pollChoices"
:author="appearNote.user"
:emojiUrls="appearNote.emojis"
:class="$style.poll"
@ -113,9 +113,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkReactionsViewer
v-if="appearNote.reactionAcceptance !== 'likeOnly'"
style="margin-top: 6px;"
:reactions="reactions"
:reactionEmojis="reactionEmojis"
:myReaction="myReaction"
:reactions="$appearNote.reactions"
:reactionEmojis="$appearNote.reactionEmojis"
:myReaction="$appearNote.myReaction"
:noteId="appearNote.id"
:maxNumber="16"
@mockUpdateMyReaction="emitUpdReaction"
@ -143,11 +143,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ti ti-ban"></i>
</button>
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
<i v-else-if="myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && $appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
<i v-else-if="$appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ti ti-plus"></i>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && reactionCount > 0" :class="$style.footerButtonCount">{{ number(reactionCount) }}</p>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && $appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number($appearNote.reactionCount) }}</p>
</button>
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
<i class="ti ti-paperclip"></i>
@ -194,7 +194,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, ref, useTemplateRef, watch, provide, shallowRef } from 'vue';
import { computed, inject, onMounted, ref, useTemplateRef, watch, provide, shallowRef, reactive } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@ -287,11 +287,13 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note);
const appearNote = getAppearNote(note);
const reactions = ref(appearNote.reactions);
const reactionCount = ref(appearNote.reactionCount);
const reactionEmojis = ref(appearNote.reactionEmojis);
const myReaction = ref(appearNote.myReaction);
const pollChoices = ref(appearNote.poll?.choices);
const $appearNote = reactive({
reactions: appearNote.reactions,
reactionCount: appearNote.reactionCount,
reactionEmojis: appearNote.reactionEmojis,
myReaction: appearNote.myReaction,
pollChoices: appearNote.poll?.choices,
});
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
@ -317,7 +319,7 @@ const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibili
const renoteCollapsed = ref(
prefer.s.collapseRenotes && isRenote && (
($i && ($i.id === note.userId || $i.id === appearNote.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
(myReaction.value != null)
($appearNote.myReaction != null)
),
);
@ -421,12 +423,7 @@ if (props.mock) {
useNoteCapture({
note: appearNote,
parentNote: note,
reactionsRef: reactions,
reactionCountRef: reactionCount,
reactionEmojisRef: reactionEmojis,
myReactionRef: myReaction,
pollChoicesRef: pollChoices,
isDeletedRef: isDeleted,
$note: $appearNote,
});
}
@ -456,7 +453,7 @@ if (!props.mock) {
const reactions = await misskeyApiGet('notes/reactions', {
noteId: appearNote.id,
limit: 10,
_cacheKey_: reactionCount.value,
_cacheKey_: $appearNote.reactionCount,
});
const users = reactions.map(x => x.user);
@ -467,7 +464,7 @@ if (!props.mock) {
showing,
reaction: '❤️',
users,
count: reactionCount.value,
count: $appearNote.reactionCount,
targetElement: reactButton.value!,
}, {
closed: () => dispose(),
@ -566,7 +563,7 @@ function react(): void {
}
function undoReact(): void {
const oldReaction = myReaction.value;
const oldReaction = $appearNote.myReaction;
if (!oldReaction) return;
if (props.mock) {
@ -580,7 +577,7 @@ function undoReact(): void {
}
function toggleReact() {
if (myReaction.value == null) {
if ($appearNote.myReaction == null) {
react();
} else {
undoReact();

View File

@ -136,9 +136,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkReactionsViewer
v-if="appearNote.reactionAcceptance !== 'likeOnly'"
style="margin-top: 6px;"
:reactions="reactions"
:reactionEmojis="reactionEmojis"
:myReaction="myReaction"
:reactions="$appearNote.reactions"
:reactionEmojis="$appearNote.reactionEmojis"
:myReaction="$appearNote.myReaction"
:noteId="appearNote.id"
:maxNumber="16"
@mockUpdateMyReaction="emitUpdReaction"
@ -161,11 +161,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ti ti-ban"></i>
</button>
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
<i v-else-if="myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && $appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
<i v-else-if="$appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ti ti-plus"></i>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(reactionCount) }}</p>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && $appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number($appearNote.reactionCount) }}</p>
</button>
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
<i class="ti ti-paperclip"></i>
@ -229,7 +229,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue';
import { computed, inject, onMounted, provide, reactive, ref, useTemplateRef } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@ -308,11 +308,13 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note);
const appearNote = getAppearNote(note);
const reactions = ref(appearNote.reactions);
const reactionCount = ref(appearNote.reactionCount);
const reactionEmojis = ref(appearNote.reactionEmojis);
const myReaction = ref(appearNote.myReaction);
const pollChoices = ref(appearNote.poll?.choices);
const $appearNote = reactive({
reactions: appearNote.reactions,
reactionCount: appearNote.reactionCount,
reactionEmojis: appearNote.reactionEmojis,
myReaction: appearNote.myReaction,
pollChoices: appearNote.poll?.choices,
});
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
@ -376,7 +378,7 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
const tab = ref(props.initialTab);
const reactionTabType = ref<string | null>(null);
const renotesPagination = computed<Paging>(() => ({
const renotesPagination = computed(() => ({
endpoint: 'notes/renotes',
limit: 10,
params: {
@ -384,7 +386,7 @@ const renotesPagination = computed<Paging>(() => ({
},
}));
const reactionsPagination = computed<Paging>(() => ({
const reactionsPagination = computed(() => ({
endpoint: 'notes/reactions',
limit: 10,
params: {
@ -396,12 +398,7 @@ const reactionsPagination = computed<Paging>(() => ({
useNoteCapture({
note: appearNote,
parentNote: note,
reactionsRef: reactions,
reactionCountRef: reactionCount,
reactionEmojisRef: reactionEmojis,
myReactionRef: myReaction,
pollChoicesRef: pollChoices,
isDeletedRef: isDeleted,
$note: $appearNote,
});
useTooltip(renoteButton, async (showing) => {
@ -429,7 +426,7 @@ if (appearNote.reactionAcceptance === 'likeOnly') {
const reactions = await misskeyApiGet('notes/reactions', {
noteId: appearNote.id,
limit: 10,
_cacheKey_: reactionCount.value,
_cacheKey_: $appearNote.reactionCount,
});
const users = reactions.map(x => x.user);
@ -440,7 +437,7 @@ if (appearNote.reactionAcceptance === 'likeOnly') {
showing,
reaction: '❤️',
users,
count: reactionCount.value,
count: $appearNote.reactionCount,
targetElement: reactButton.value!,
}, {
closed: () => dispose(),

View File

@ -6,7 +6,7 @@
import { onUnmounted } from 'vue';
import * as Misskey from 'misskey-js';
import { EventEmitter } from 'eventemitter3';
import type { Ref } from 'vue';
import type { Reactive, Ref } from 'vue';
import { useStream } from '@/stream.js';
import { $i } from '@/i.js';
import { store } from '@/store.js';
@ -86,17 +86,14 @@ window.setInterval(() => {
function pollingSubscribe(props: {
note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>;
reactionsRef: Ref<Misskey.entities.Note['reactions']>;
reactionCountRef: Ref<Misskey.entities.Note['reactionCount']>;
reactionEmojisRef: Ref<Misskey.entities.Note['reactionEmojis']>;
isDeletedRef: Ref<boolean>;
$note: ReactiveNoteData;
}) {
const { note, reactionsRef, reactionCountRef, reactionEmojisRef } = props;
const { note, $note } = props;
function onFetched(data: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>): void {
reactionsRef.value = data.reactions;
reactionCountRef.value = Object.values(data.reactions).reduce((a, b) => a + b, 0);
reactionEmojisRef.value = data.reactionEmojis;
$note.reactions = data.reactions;
$note.reactionCount = Object.values(data.reactions).reduce((a, b) => a + b, 0);
$note.reactionEmojis = data.reactionEmojis;
}
pollingEnqueue(note);
@ -177,17 +174,20 @@ function realtimeSubscribe(props: {
});
}
type ReactiveNoteData = Reactive<{
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;
reactionsRef: Ref<Misskey.entities.Note['reactions']>;
reactionCountRef: Ref<Misskey.entities.Note['reactionCount']>;
reactionEmojisRef: Ref<Misskey.entities.Note['reactionEmojis']>;
myReactionRef: Ref<Misskey.entities.Note['myReaction']>;
pollChoicesRef: Ref<NonNullable<Misskey.entities.Note['poll']>['choices'] | null>;
isDeletedRef: Ref<boolean>;
$note: ReactiveNoteData;
}) {
const { note, parentNote, reactionsRef, reactionCountRef, reactionEmojisRef, myReactionRef, pollChoicesRef } = props;
const { note, parentNote, $note } = props;
noteEvents.on(`reacted:${note.id}`, onReacted);
noteEvents.on(`unreacted:${note.id}`, onUnreacted);
@ -203,17 +203,17 @@ export function useNoteCapture(props: {
if (newReactedKey === latestReactedKey) return;
latestReactedKey = newReactedKey;
if (ctx.emoji && !(ctx.emoji.name in reactionEmojisRef.value)) {
reactionEmojisRef.value[ctx.emoji.name] = ctx.emoji.url;
if (ctx.emoji && !(ctx.emoji.name in $note.reactionEmojis)) {
$note.reactionEmojis[ctx.emoji.name] = ctx.emoji.url;
}
const currentCount = reactionsRef.value[ctx.reaction] || 0;
const currentCount = $note.reactions[ctx.reaction] || 0;
reactionsRef.value[ctx.reaction] = currentCount + 1;
reactionCountRef.value += 1;
$note.reactions[ctx.reaction] = currentCount + 1;
$note.reactionCount += 1;
if ($i && (ctx.userId === $i.id)) {
myReactionRef.value = ctx.reaction;
$note.myReaction = ctx.reaction;
}
}
@ -222,14 +222,14 @@ export function useNoteCapture(props: {
if (newUnreactedKey === latestUnreactedKey) return;
latestUnreactedKey = newUnreactedKey;
const currentCount = reactionsRef.value[ctx.reaction] || 0;
const currentCount = $note.reactions[ctx.reaction] || 0;
reactionsRef.value[ctx.reaction] = Math.max(0, currentCount - 1);
reactionCountRef.value = Math.max(0, reactionCountRef.value - 1);
if (reactionsRef.value[ctx.reaction] === 0) delete reactionsRef.value[ctx.reaction];
$note.reactions[ctx.reaction] = 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 ($i && (ctx.userId === $i.id)) {
myReactionRef.value = null;
$note.myReaction = null;
}
}
@ -238,7 +238,7 @@ export function useNoteCapture(props: {
if (newPollVotedKey === latestPollVotedKey) return;
latestPollVotedKey = newPollVotedKey;
const choices = [...pollChoicesRef.value];
const choices = [...$note.pollChoices];
choices[ctx.choice] = {
...choices[ctx.choice],
votes: choices[ctx.choice].votes + 1,
@ -247,11 +247,11 @@ export function useNoteCapture(props: {
} : {}),
};
pollChoicesRef.value = choices;
$note.pollChoices = choices;
}
function onDeleted(): void {
props.isDeletedRef.value = true;
$note.isDeleted = true;
}
onUnmounted(() => {