<template> <div class="menu"> <div class="notifs-header"> <h4 class="notifs-heading">Notifications</h4> <div class="notifs-actions" > <Icon icon="stack-check" @click="markAllSeen" /> <Icon v-close-popper icon="plus3" @click="emit('addAlert')" /> </div> </div> <ul v-if="done" class="notifs nolist" > <li v-for="notif in notifications" :key="notif.id" class="notif" :class="{ unseen: !notif.isSeen }" @click="markSeen(notif)" > <a :href="`/scene/${notif.scene.id}/${notif.scene.slug}`" target="_blank" class="notif-body notif-link nolink" > <span class="notif-trigger"> <span class="notif-details"> New <template v-if="notif.matchedTags.length > 0" >{{ notif.matchedTags.map((tag) => tag.name).join(', ') }}</template> scene <template v-if="notif.matchedActors.length > 0" >with {{ notif.matchedActors.map((actor) => actor.name).join(', ') }} </template> <template v-if="notif.matchedEntity" >for {{ notif.matchedEntity.name }} </template> <template v-if="notif.matchedExpressions.length > 0" >matching {{ notif.matchedExpressions.map((match) => match.expression).join(', ') }}</template> </span> <span v-if="notif.alertId" class="notif-id" title="Alert ID" >#{{ notif.alertId }}</span> </span> <span class="notif-scene"> <span class="notif-date">{{ formatDate(notif.scene.effectiveDate, 'MMM d') }}</span> <span class="notif-title ellipsis">{{ notif.scene.title }}</span> </span> </a> </li> </ul> <Ellipsis v-else class="notifs loading" /> </div> </template> <script setup> import { ref, defineEmits, onMounted, inject, } from 'vue'; import { get, patch } from '#/src/api.js'; import { formatDate } from '#/utils/format.js'; import Ellipsis from '#/components/loading/ellipsis.vue'; const pageContext = inject('pageContext'); const user = pageContext.user; const notifications = ref([]); const done = ref(true); const emit = defineEmits(['unseen', 'addAlert']); async function fetchNotifications() { done.value = false; const res = await get(`/users/${user?.id}/notifications`); notifications.value = res.notifications; done.value = true; emit('unseen', res.unseen); } async function markSeen(notif) { if (notif.isSeen || !done.value) { return; } done.value = false; await patch(`/users/${user?.id}/notifications/${notif.id}`, { seen: true, }); await fetchNotifications(); done.value = true; } async function markAllSeen() { done.value = false; await patch(`/users/${user?.id}/notifications`, { seen: true, }); await fetchNotifications(); done.value = true; } onMounted(async () => { await fetchNotifications(); }); </script> <style scoped> .notifs { width: 30rem; max-height: 20rem; max-width: 100%; height: 100%; overflow-y: auto; overflow-x: hidden; } .loading { display: flex; justify-content: center; } .notifs-header { display: flex; justify-content: space-between; box-shadow: inset 0 0 3px var(--shadow-weak-30); .icon { fill: var(--glass-strong-10); &:hover { fill: var(--primary); cursor: pointer; } } } .notifs-heading { font-size: 1rem; padding: .75rem 1rem; margin: 0; font-weight: normal; } .notifs-actions { align-items: stretch; .icon { height: 100%; padding: 0 .75rem; &:last-child { padding-right: 1rem; } } } .notif { display: flex; align-items: center; width: 100%; overflow: hidden; &:not(:last-child) { border-bottom: solid 1px var(--glass-weak-40); } &:before { width: 2.5rem; flex-shrink: 0; content: '•'; color: var(--glass-weak-20); text-align: center; } &.unseen:before { color: var(--primary); } &.unseen:hover:before { content: '✓'; } } .notif-body { display: flex; flex-direction: column; flex-grow: 1; overflow: hidden; box-sizing: border-box; font-size: .9rem; } .notif-trigger { display: flex; justify-content: space-between; padding: .5rem 0 .1rem 0; box-sizing: border-box; color: var(--glass-strong); font-weight: bold; } .notif-details { line-height: 1.25; } .notif-link { overflow: hidden; } .notif-scene { display: flex; padding: .15rem 0 .5rem 0; } .notif-date { flex-shrink: 0; color: var(--glass-strong-10); &:after { content: '•'; padding: 0 .5rem; color: var(--glass-weak-20); } } .notif-thumb { width: 5rem; height: 3rem; object-fit: cover; } .notif-id { padding: 0 .5rem; color: var(--glass-weak-10); font-size: .9rem; font-weight: normal; } </style>