hippofish/packages/frontend/src/components/MkReactionsViewer.reaction.vue

242 lines
6 KiB
Vue
Raw Normal View History

<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
2020-02-21 22:43:46 +01:00
<button
2023-01-08 06:28:14 +01:00
ref="buttonEl"
2021-12-27 14:59:14 +01:00
v-ripple="canToggle"
2023-01-14 09:23:49 +01:00
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()"
@contextmenu.prevent.stop="menu"
>
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()" @click.stop/>
2023-01-14 09:23:49 +01:00
<span :class="$style.count">{{ count }}</span>
2020-02-21 22:43:46 +01:00
</button>
</template>
2022-06-30 16:51:18 +02:00
<script lang="ts" setup>
import { computed, inject, onMounted, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
refactor: frontendのcomponentsの型エラーを改善 (#12926) * add: safeFloatParserを追加 * fix: 欠けていた型を追加 * refactor: pageBlockTypesをjson-schemaに移植 * refactor: components/global内の型エラーが出ている箇所を修正 * lint: fix null check style * refactor: fix type error * refactor: fix some type errors * fix: 翻訳が抜けていた箇所を修正 * refactor: getJsonSchemaで正しいスキーマが返されるように修正 * fix: MkChartの型エラーとbytesオプションが機能していない問題を修正 * fix(misskey-js): `drive`->`folderUpdated`のpayloadの型が間違っていたのを修正 * refactor: fix some type errors * change: Captcha読み込み中の文言をLoadingに変更 * refactor(backend/misskey-js): MainEventの型を改善 * refactor: chartjs-plugin-gradientが二重でpluginに登録されていたのを修正 * update: misskey-js.api.md * refactor: fix some type errors * fix: backendのtypecheckが落ちていたのを修正 * update: misskey-js.api.md * add: json-schemaのnoteにpollの型定義を追加 * refactor: noteのjson-schemaの型を改善 * refactor: MkPoll * refactor: fix some type errors * change: UserLiteにisLockedを持たせるように * fix: notificationスキーマにroleが含まれていないのを修正 * Revert "change: UserLiteにisLockedを持たせるように" This reverts commit 1bb0c8e7a9b19a4e9f21bf7381712b98f27672a5. * fix: フォロー通知から鍵垢へのフォローを行うと処理中のまま止まってしまう問題を修正 * refactor: noteスキーマのvisibilityにenumを追加 * change: deepCloneのCloneableTypeにundefinedを追加 * refactor: fix some type errors * refactor: `allowEmpty: false`を使用していた箇所を`minLength: 1`に置き換え * enhance: API 'retension' のresponseの型を追加 * fix: Chart関連のtooltipが正しい位置に表示されない問題を修正 * refactor: fix some type errors * fix: 型情報が不足していたのを修正 * enhance: announcementスキーマにenumを追加 * enhance: ロールポリシーの型定義をRoleServiceからjson-schemaに移植 * refactor: policiesを`ref: RolePolicies`に統一 * fix: API `meta` のレスポンスの型にpoliciesが含まれていないのを修正 * refactor: fix some type errors * fix: backendのlintが落ちているのを修正 * fix: MkFoldableSectionの開閉時のanimationが適用されていない問題を修正 * fix: backendのtypecheckが落ちているのを修正 * update: run build-misskey-js-with-types * fix: MkDialogのmount時に文字数制限の判定が行われない問題を修正 * update: CHANGELOG.md * refactor: MkUserSelectDialogの型を改善 * fix: deepCloneでundefinedはcloneしないように (#9207) * change: frontendのcloneをbackend側にも反映 * update: CHANGELOG.md * fix: RoleServiceからPackを通して型RolePoliciesに依存させないように * Update packages/frontend/src/scripts/get-note-summary.ts * revert RoleService.ts changes * change: optional chaining -> non-null assertion * remove: unused import * fix: propsで渡されたuserがUserLiteの場合に意図しない動作になってしまうのを修正 * change: fix null check style * refactor: fix type error * change: fix null check style * Update packages/frontend/src/components/MkDrive.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: css moduleでglobalを使わないように * refactor: roleのiconUrlは必ず存在するものとして扱うように * enhance: MenuButtonのactiveにcomputedを受け付けられるように * Update packages/frontend/src/components/MkNotePreview.vue * Update MkWindow.vue * refactor: notification.noteは必ず存在するものとして扱うように * Update packages/frontend/src/components/MkNotification.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * fix: MkSignupDialogでdoneのemit時にresを含んでいなかったのを修正 * Update packages/frontend/src/scripts/clone.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: 不要な返り値の型を削除 * refactor: 不要なnullチェックを削除 * update: misskey-js-autogen * update: clone.ts * refactor * Update MkNotification.vue * Update MkNotification.vue * :v: * Update MkNotification.vue * Update MkNotification.vue * Update MkNotification.vue * Update MkNotifications.vue * Update MkUserSetupDialog.Profile.vue * Update MkUserCardMini.vue * :v: * Update MkMenu.vue --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-30 11:53:53 +01:00
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
import XDetails from '@/components/MkReactionsViewer.details.vue';
2023-01-08 06:22:04 +01:00
import MkReactionIcon from '@/components/MkReactionIcon.vue';
2023-09-19 09:37:43 +02:00
import * as os from '@/os.js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
2023-09-19 09:37:43 +02:00
import { useTooltip } from '@/scripts/use-tooltip.js';
import { $i } from '@/account.js';
2023-01-08 08:17:42 +01:00
import MkReactionEffect from '@/components/MkReactionEffect.vue';
2023-09-19 09:37:43 +02:00
import { claimAchievement } from '@/scripts/achievements.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as sound from '@/scripts/sound.js';
2024-02-06 21:23:37 +01:00
import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
2024-03-02 17:36:49 +01:00
import { customEmojisMap } from '@/custom-emojis.js';
import { getUnicodeEmoji } from '@/scripts/emojilist.js';
2022-06-30 16:51:18 +02:00
const props = defineProps<{
reaction: string;
count: number;
isInitial: boolean;
note: Misskey.entities.Note;
2022-06-30 16:51:18 +02:00
}>();
const mock = inject<boolean>('mock', false);
const emit = defineEmits<{
(ev: 'reactionToggled', emoji: string, newCount: number): void;
}>();
2023-01-08 06:28:14 +01:00
const buttonEl = shallowRef<HTMLElement>();
2022-06-30 16:51:18 +02:00
2024-03-02 17:36:49 +01:00
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
2024-02-06 21:23:37 +01:00
const canToggle = computed(() => {
2024-03-02 17:36:49 +01:00
return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
2024-02-06 21:23:37 +01:00
});
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
2022-06-30 16:51:18 +02:00
async function toggleReaction() {
2022-06-30 16:51:18 +02:00
if (!canToggle.value) return;
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;
if (oldReaction !== props.reaction) {
sound.playMisskeySfx('reaction');
}
if (mock) {
emit('reactionToggled', props.reaction, (props.count - 1));
return;
}
misskeyApi('notes/reactions/delete', {
2022-06-30 16:51:18 +02:00
noteId: props.note.id,
}).then(() => {
if (oldReaction !== props.reaction) {
misskeyApi('notes/reactions/create', {
noteId: props.note.id,
2022-06-30 16:51:18 +02:00
reaction: props.reaction,
});
}
});
2022-06-30 16:51:18 +02:00
} else {
sound.playMisskeySfx('reaction');
if (mock) {
emit('reactionToggled', props.reaction, (props.count + 1));
return;
}
misskeyApi('notes/reactions/create', {
2022-06-30 16:51:18 +02:00
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');
}
2022-06-30 16:51:18 +02:00
}
}
async function menu(ev) {
2024-02-06 21:23:37 +01:00
if (!canGetInfo.value) return;
os.popupMenu([{
text: i18n.ts.info,
2024-06-21 17:27:36 +02:00
icon: 'ti ti-info-circle',
action: async () => {
const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
emoji: await misskeyApiGet('emoji', {
name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
}),
}, {
closed: () => dispose(),
});
},
}], ev.currentTarget ?? ev.target);
}
function anime() {
refactor: frontendのcomponentsの型エラーを改善 (#12926) * add: safeFloatParserを追加 * fix: 欠けていた型を追加 * refactor: pageBlockTypesをjson-schemaに移植 * refactor: components/global内の型エラーが出ている箇所を修正 * lint: fix null check style * refactor: fix type error * refactor: fix some type errors * fix: 翻訳が抜けていた箇所を修正 * refactor: getJsonSchemaで正しいスキーマが返されるように修正 * fix: MkChartの型エラーとbytesオプションが機能していない問題を修正 * fix(misskey-js): `drive`->`folderUpdated`のpayloadの型が間違っていたのを修正 * refactor: fix some type errors * change: Captcha読み込み中の文言をLoadingに変更 * refactor(backend/misskey-js): MainEventの型を改善 * refactor: chartjs-plugin-gradientが二重でpluginに登録されていたのを修正 * update: misskey-js.api.md * refactor: fix some type errors * fix: backendのtypecheckが落ちていたのを修正 * update: misskey-js.api.md * add: json-schemaのnoteにpollの型定義を追加 * refactor: noteのjson-schemaの型を改善 * refactor: MkPoll * refactor: fix some type errors * change: UserLiteにisLockedを持たせるように * fix: notificationスキーマにroleが含まれていないのを修正 * Revert "change: UserLiteにisLockedを持たせるように" This reverts commit 1bb0c8e7a9b19a4e9f21bf7381712b98f27672a5. * fix: フォロー通知から鍵垢へのフォローを行うと処理中のまま止まってしまう問題を修正 * refactor: noteスキーマのvisibilityにenumを追加 * change: deepCloneのCloneableTypeにundefinedを追加 * refactor: fix some type errors * refactor: `allowEmpty: false`を使用していた箇所を`minLength: 1`に置き換え * enhance: API 'retension' のresponseの型を追加 * fix: Chart関連のtooltipが正しい位置に表示されない問題を修正 * refactor: fix some type errors * fix: 型情報が不足していたのを修正 * enhance: announcementスキーマにenumを追加 * enhance: ロールポリシーの型定義をRoleServiceからjson-schemaに移植 * refactor: policiesを`ref: RolePolicies`に統一 * fix: API `meta` のレスポンスの型にpoliciesが含まれていないのを修正 * refactor: fix some type errors * fix: backendのlintが落ちているのを修正 * fix: MkFoldableSectionの開閉時のanimationが適用されていない問題を修正 * fix: backendのtypecheckが落ちているのを修正 * update: run build-misskey-js-with-types * fix: MkDialogのmount時に文字数制限の判定が行われない問題を修正 * update: CHANGELOG.md * refactor: MkUserSelectDialogの型を改善 * fix: deepCloneでundefinedはcloneしないように (#9207) * change: frontendのcloneをbackend側にも反映 * update: CHANGELOG.md * fix: RoleServiceからPackを通して型RolePoliciesに依存させないように * Update packages/frontend/src/scripts/get-note-summary.ts * revert RoleService.ts changes * change: optional chaining -> non-null assertion * remove: unused import * fix: propsで渡されたuserがUserLiteの場合に意図しない動作になってしまうのを修正 * change: fix null check style * refactor: fix type error * change: fix null check style * Update packages/frontend/src/components/MkDrive.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: css moduleでglobalを使わないように * refactor: roleのiconUrlは必ず存在するものとして扱うように * enhance: MenuButtonのactiveにcomputedを受け付けられるように * Update packages/frontend/src/components/MkNotePreview.vue * Update MkWindow.vue * refactor: notification.noteは必ず存在するものとして扱うように * Update packages/frontend/src/components/MkNotification.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * fix: MkSignupDialogでdoneのemit時にresを含んでいなかったのを修正 * Update packages/frontend/src/scripts/clone.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: 不要な返り値の型を削除 * refactor: 不要なnullチェックを削除 * update: misskey-js-autogen * update: clone.ts * refactor * Update MkNotification.vue * Update MkNotification.vue * :v: * Update MkNotification.vue * Update MkNotification.vue * Update MkNotification.vue * Update MkNotifications.vue * Update MkUserSetupDialog.Profile.vue * Update MkUserCardMini.vue * :v: * Update MkMenu.vue --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-30 11:53:53 +01:00
if (document.hidden || !defaultStore.state.animation || buttonEl.value == null) return;
2022-06-30 16:51:18 +02:00
2023-01-08 06:28:14 +01:00
const rect = buttonEl.value.getBoundingClientRect();
const x = rect.left + 16;
const y = rect.top + (buttonEl.value.offsetHeight / 2);
const { dispose } = os.popup(MkReactionEffect, { reaction: props.reaction, x, y }, {
end: () => dispose(),
});
}
2022-06-30 16:51:18 +02:00
watch(() => props.count, (newCount, oldCount) => {
if (oldCount < newCount) anime();
});
2022-06-30 16:51:18 +02:00
onMounted(() => {
if (!props.isInitial) anime();
});
if (!mock) {
useTooltip(buttonEl, async (showing) => {
const reactions = await misskeyApiGet('notes/reactions', {
noteId: props.note.id,
type: props.reaction,
limit: 10,
_cacheKey_: props.count,
});
const users = reactions.map(x => x.user);
const { dispose } = os.popup(XDetails, {
showing,
reaction: props.reaction,
users,
count: props.count,
targetElement: buttonEl.value,
}, {
closed: () => dispose(),
});
}, 100);
}
</script>
2023-01-14 09:23:49 +01:00
<style lang="scss" module>
.root {
display: inline-flex;
height: 42px;
margin: 2px;
padding: 0 6px;
font-size: 1.5em;
border-radius: var(--radius-sm);
align-items: center;
justify-content: center;
2019-09-02 23:20:04 +02:00
2020-04-15 17:47:17 +02:00
&.canToggle {
background: var(--buttonBg);
2020-04-15 17:47:17 +02:00
&:hover {
background: rgba(0, 0, 0, 0.1);
}
}
2020-04-15 17:47:17 +02:00
&: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 {
2023-09-18 06:25:49 +02:00
background: var(--accentedBg);
color: var(--accent);
box-shadow: 0 0 0 1px var(--accent) inset;
2020-04-17 13:30:12 +02:00
> .count {
color: var(--accent);
}
> .icon {
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
}
}
2023-01-14 09:23:49 +01:00
}
.limitWidth {
2023-12-27 07:15:08 +01:00
max-width: 70px;
object-fit: contain;
2023-09-21 03:29:40 +02:00
}
2023-01-14 09:23:49 +01:00
.count {
font-size: 0.7em;
line-height: 42px;
2023-01-14 09:23:49 +01:00
margin: 0 0 0 4px;
}
</style>