diff --git a/frontend/src/components/feather/FeatherPostCard.vue b/frontend/src/components/feather/FeatherPostCard.vue index 5e27fe9..934acc2 100644 --- a/frontend/src/components/feather/FeatherPostCard.vue +++ b/frontend/src/components/feather/FeatherPostCard.vue @@ -8,18 +8,14 @@ import { Share2, UserRound, } from 'lucide-vue-next' -import { - SkyButton as kButton, - SkyGlass as kGlass, - SkyIcon as kIcon, -} from '@/ui' -import { computed, ref } from 'vue' +import { SkyButton, SkyGlass, SkyIcon } from '@/ui' +import { computed, nextTick, onBeforeUnmount, ref } from 'vue' import { usePhoneStore } from '@/stores/phone' import type { FeatherPost } from '@/types/feather' const props = defineProps<{ post: FeatherPost }>() -defineEmits<{ +const emit = defineEmits<{ follow: [post: FeatherPost] media: [post: FeatherPost, index: number] menu: [post: FeatherPost] @@ -32,6 +28,9 @@ defineEmits<{ const phone = usePhoneStore() const expanded = ref(false) +const reactionPulse = ref<'like' | 'bookmark' | null>(null) +let reactionPulseFrame: number | null = null +let reactionPulseTimer: number | null = null const shouldTruncate = computed(() => props.post.body.length > 260) const visibleBody = computed(() => shouldTruncate.value && !expanded.value @@ -57,10 +56,40 @@ function relativeTime(timestamp: number): string { month: 'short', }).format(timestamp) } + +function playReactionPulse(kind: 'like' | 'bookmark'): void { + if (reactionPulseFrame !== null) + window.cancelAnimationFrame(reactionPulseFrame) + if (reactionPulseTimer !== null) window.clearTimeout(reactionPulseTimer) + reactionPulse.value = null + void nextTick(() => { + reactionPulseFrame = window.requestAnimationFrame(() => { + reactionPulse.value = kind + reactionPulseFrame = null + reactionPulseTimer = window.setTimeout(() => { + reactionPulse.value = null + reactionPulseTimer = null + }, 520) + }) + }) +} + +function react(kind: 'like' | 'bookmark'): void { + const isActivating = + kind === 'like' ? !props.post.is_liked : !props.post.is_bookmarked + if (isActivating) playReactionPulse(kind) + emit('react', props.post, kind) +} + +onBeforeUnmount(() => { + if (reactionPulseFrame !== null) + window.cancelAnimationFrame(reactionPulseFrame) + if (reactionPulseTimer !== null) window.clearTimeout(reactionPulseTimer) +})