mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-26 04:33:08 +02:00
ae5d052274
to keep things manageable i merged a lot of one off values into just a handful of common sizes, so some parts of the ui will look different than upstream even with the "Misskey" rounding mode
182 lines
4.1 KiB
Vue
182 lines
4.1 KiB
Vue
<!--
|
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
-->
|
|
|
|
<template>
|
|
<button
|
|
ref="buttonEl"
|
|
v-ripple="canToggle"
|
|
class="_button"
|
|
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
|
|
@click="toggleReaction()"
|
|
>
|
|
<MkReactionIcon :class="$style.icon" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
|
<span :class="$style.count">{{ count }}</span>
|
|
</button>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, onMounted, shallowRef, watch } from 'vue';
|
|
import * as Misskey from 'misskey-js';
|
|
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
|
import * as os from '@/os.js';
|
|
import { useTooltip } from '@/scripts/use-tooltip.js';
|
|
import { $i } from '@/account.js';
|
|
import MkReactionEffect from '@/components/MkReactionEffect.vue';
|
|
import { claimAchievement } from '@/scripts/achievements.js';
|
|
import { defaultStore } from '@/store.js';
|
|
import { i18n } from '@/i18n.js';
|
|
|
|
const props = defineProps<{
|
|
reaction: string;
|
|
count: number;
|
|
isInitial: boolean;
|
|
note: Misskey.entities.Note;
|
|
}>();
|
|
|
|
const buttonEl = shallowRef<HTMLElement>();
|
|
|
|
const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
|
|
|
|
async function toggleReaction() {
|
|
if (!canToggle.value) return;
|
|
|
|
// TODO: その絵文字を使う権限があるかどうか確認
|
|
|
|
const oldReaction = props.note.myReaction;
|
|
if (oldReaction) {
|
|
const confirm = await os.confirm({
|
|
type: 'warning',
|
|
text: oldReaction !== props.reaction ? i18n.ts.changeReactionConfirm : i18n.ts.cancelReactionConfirm,
|
|
});
|
|
if (confirm.canceled) return;
|
|
|
|
os.api('notes/reactions/delete', {
|
|
noteId: props.note.id,
|
|
}).then(() => {
|
|
if (oldReaction !== props.reaction) {
|
|
os.api('notes/reactions/create', {
|
|
noteId: props.note.id,
|
|
reaction: props.reaction,
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
os.api('notes/reactions/create', {
|
|
noteId: props.note.id,
|
|
reaction: props.reaction,
|
|
});
|
|
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
|
|
claimAchievement('reactWithoutRead');
|
|
}
|
|
}
|
|
}
|
|
|
|
function anime() {
|
|
if (document.hidden) return;
|
|
if (!defaultStore.state.animation) return;
|
|
|
|
const rect = buttonEl.value.getBoundingClientRect();
|
|
const x = rect.left + 16;
|
|
const y = rect.top + (buttonEl.value.offsetHeight / 2);
|
|
os.popup(MkReactionEffect, { reaction: props.reaction, x, y }, {}, 'end');
|
|
}
|
|
|
|
watch(() => props.count, (newCount, oldCount) => {
|
|
if (oldCount < newCount) anime();
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (!props.isInitial) anime();
|
|
});
|
|
|
|
useTooltip(buttonEl, async (showing) => {
|
|
const reactions = await os.apiGet('notes/reactions', {
|
|
noteId: props.note.id,
|
|
type: props.reaction,
|
|
limit: 11,
|
|
_cacheKey_: props.count,
|
|
});
|
|
|
|
const users = reactions.map(x => x.user);
|
|
|
|
os.popup(XDetails, {
|
|
showing,
|
|
reaction: props.reaction,
|
|
users,
|
|
count: props.count,
|
|
targetElement: buttonEl.value,
|
|
}, {}, 'closed');
|
|
}, 100);
|
|
</script>
|
|
|
|
<style lang="scss" module>
|
|
.root {
|
|
display: inline-block;
|
|
height: 42px;
|
|
margin: 2px;
|
|
padding: 0 6px;
|
|
font-size: 1.5em;
|
|
border-radius: var(--radius-sm);
|
|
|
|
&.canToggle {
|
|
background: var(--buttonBg);
|
|
|
|
&:hover {
|
|
background: rgba(0, 0, 0, 0.1);
|
|
}
|
|
}
|
|
|
|
&:not(.canToggle) {
|
|
cursor: default;
|
|
}
|
|
|
|
&.small {
|
|
height: 32px;
|
|
font-size: 1em;
|
|
border-radius: var(--radius-xs);
|
|
|
|
> .count {
|
|
font-size: 0.9em;
|
|
line-height: 32px;
|
|
}
|
|
}
|
|
|
|
&.large {
|
|
height: 52px;
|
|
font-size: 2em;
|
|
border-radius: var(--radius-sm);
|
|
|
|
> .count {
|
|
font-size: 0.6em;
|
|
line-height: 52px;
|
|
}
|
|
}
|
|
|
|
&.reacted, &.reacted:hover {
|
|
background: var(--accentedBg);
|
|
color: var(--accent);
|
|
box-shadow: 0 0 0px 1px var(--accent) inset;
|
|
|
|
> .count {
|
|
color: var(--accent);
|
|
}
|
|
|
|
> .icon {
|
|
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
|
}
|
|
}
|
|
}
|
|
|
|
.icon {
|
|
max-width: 150px;
|
|
}
|
|
|
|
.count {
|
|
font-size: 0.7em;
|
|
line-height: 42px;
|
|
margin: 0 0 0 4px;
|
|
}
|
|
</style>
|