Added notification bell, WIP.

This commit is contained in:
DebaucheryLibrarian 2024-05-20 06:29:10 +02:00
parent dc5affb4cf
commit 342ba6191e
7 changed files with 141 additions and 53 deletions

View File

@ -91,7 +91,7 @@
<div class="result-label"> <div class="result-label">
{{ actor.name }} {{ actor.name }}
<template v-if="actor.ageFromBirth || actor.origin?.country">({{ [actor.ageFromBirth, actor.origin?.country?.alpha2].join(', ') }})</template> <template v-if="actor.ageFromBirth || actor.origin?.country">({{ [actor.ageFromBirth, actor.origin?.country?.alpha2].filter(Boolean).join(', ') }})</template>
</div> </div>
</li> </li>
</ul> </ul>

View File

@ -78,69 +78,92 @@
</button> </button>
</form> </form>
<VDropdown <div
v-if="user" v-if="user"
:triggers="['click']" class="userpanel"
:prevent-overflow="true" :class="{ searching: searchFocused }"
> >
<div <VDropdown
class="userpanel" :triggers="['click']"
:class="{ searching: searchFocused }" :prevent-overflow="true"
>
<Icon
icon="bell2"
class="notifs-bell"
:class="{ unread: notifications.some((notif) => !notif.isSeen) }"
/>
<template #popper>
<div class="menu">
<ul class="notifs nolist">
<li
v-for="notif in notifications"
:key="notif.id"
class="notif"
>{{ notif.scene.title }}</li>
</ul>
</div>
</template>
</VDropdown>
<VDropdown
:triggers="['click']"
:prevent-overflow="true"
> >
<img <img
:src="user.avatar" :src="user.avatar"
class="avatar" class="avatar"
> >
</div>
<template #popper> <template #popper>
<div class="menu"> <div class="menu">
<a <a
:href="`/user/${user.username}`" :href="`/user/${user.username}`"
class="menu-header ellipsis" class="menu-header ellipsis"
>{{ user.username }}</a> >{{ user.username }}</a>
<ul class="menu-list nolist"> <ul class="menu-list nolist">
<li class="menu-item"> <li class="menu-item">
<a <a
:href="`/user/${user.username}`" :href="`/user/${user.username}`"
class="menu-button nolink" class="menu-button nolink"
>
<Icon icon="user7" />
Profile
</a>
</li>
<li
v-if="user.primaryStash"
class="menu-item"
> >
<Icon icon="user7" /> <a
Profile :href="`/stash/${user.username}/${user.primaryStash.slug}`"
</a> class="menu-button nolink favorites"
</li> >
<Icon icon="heart7" />
Favorites
</a>
</li>
<li <li
v-if="user.primaryStash" v-close-popper
class="menu-item" class="menu-item menu-button"
> @click="showSettings = true"
<a
:href="`/stash/${user.username}/${user.primaryStash.slug}`"
class="menu-button nolink favorites"
> >
<Icon icon="heart7" /> <Icon icon="equalizer" />
Favorites Settings
</a> </li>
</li>
<li <li
v-close-popper class="menu-button menu-item logout"
class="menu-item menu-button" @click="logout"
@click="showSettings = true" ><Icon icon="exit2" />Log out</li>
> </ul>
<Icon icon="equalizer" /> </div>
Settings </template>
</li> </VDropdown>
</div>
<li
class="menu-button menu-item logout"
@click="logout"
><Icon icon="exit2" />Log out</li>
</ul>
</div>
</template>
</VDropdown>
<div <div
v-else-if="allowLogin" v-else-if="allowLogin"
@ -181,11 +204,14 @@ import logo from '../../assets/img/logo.svg?raw'; // eslint-disable-line import/
const pageContext = inject('pageContext'); const pageContext = inject('pageContext');
const user = pageContext.user; const user = pageContext.user;
const notifications = pageContext.notifications;
const query = ref(pageContext.urlParsed.search.q || ''); const query = ref(pageContext.urlParsed.search.q || '');
const allowLogin = pageContext.env.allowLogin; const allowLogin = pageContext.env.allowLogin;
const searchFocused = ref(false); const searchFocused = ref(false);
const showSettings = ref(false); const showSettings = ref(false);
console.log(notifications);
const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]); const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]);
const currentPath = `${pageContext.urlParsed.pathnameOriginal}${pageContext.urlParsed.searchOriginal || ''}`; const currentPath = `${pageContext.urlParsed.pathnameOriginal}${pageContext.urlParsed.searchOriginal || ''}`;
@ -316,7 +342,7 @@ function blurSearch(event) {
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 1rem 0 1.5rem; padding: 0 1rem 0 0;
font-size: 0; font-size: 0;
cursor: pointer; cursor: pointer;
@ -332,6 +358,31 @@ function blurSearch(event) {
object-fit: cover; object-fit: cover;
} }
.notifs-bell {
padding: 0 1.25rem;
height: 100%;
fill: var(--shadow);
&:hover,
&.unread {
cursor: pointer;
fill: var(--primary);
}
}
.notifs {
overflow: auto;
}
.notif {
display: flex;
padding: .5rem 1rem;
&:not(:last-child) {
border-bottom: solid 1px var(--shadow-weak-30);
}
}
.login { .login {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -11,7 +11,7 @@ export async function onBeforeRender(pageContext) {
: [], : [],
]); ]);
console.log('out alerts', alerts); console.log(pageContext.notifications);
if (!profile) { if (!profile) {
throw render(404, `Cannot find user '${pageContext.routeParams.username}'.`); throw render(404, `Cannot find user '${pageContext.routeParams.username}'.`);

View File

@ -6,5 +6,6 @@ export default {
'urlParsed', 'urlParsed',
'env', 'env',
'user', 'user',
'notifications',
], ],
}; };

View File

@ -140,6 +140,28 @@ export async function removeAlert(alertId, reqUser) {
.delete(); .delete();
} }
export async function fetchNotifications(reqUser, options = {}) {
const notifications = await knex('notifications')
.select('notifications.*', 'alerts.id as alert_id', 'scenes.title as scene_title')
.leftJoin('releases as scenes', 'scenes.id', 'notifications.scene_id')
.leftJoin('alerts', 'alerts.id', 'notifications.alert_id')
.where('notifications.user_id', reqUser.id)
.limit(options.limit || 10)
.orderBy('created_at', 'desc');
return notifications.map((notification) => ({
id: notification.id,
sceneId: notification.scene_id,
scene: {
id: notification.scene_id,
title: notification.scene_title,
},
alertId: notification.alert_id,
isSeen: notification.seen,
createdAt: notification.created_at,
}));
}
export async function updateNotification(notificationId, updatedNotification, reqUser) { export async function updateNotification(notificationId, updatedNotification, reqUser) {
await knex('notifications') await knex('notifications')
.where('id', notificationId) .where('id', notificationId)

View File

@ -2,6 +2,7 @@ import {
fetchAlerts, fetchAlerts,
createAlert, createAlert,
removeAlert, removeAlert,
fetchNotifications,
updateNotifications, updateNotifications,
updateNotification, updateNotification,
} from '../alerts.js'; } from '../alerts.js';
@ -24,6 +25,14 @@ export async function removeAlertApi(req, res) {
res.status(204).send(); res.status(204).send();
} }
export async function fetchNotificationsApi(req, res) {
const notifications = await fetchNotifications(req.user, {
limit: req.query.limit || 10,
});
res.send(notifications);
}
export async function updateNotificationsApi(req, res) { export async function updateNotificationsApi(req, res) {
await updateNotifications(req.body, req.user); await updateNotifications(req.body, req.user);

View File

@ -51,6 +51,8 @@ import {
updateNotificationsApi, updateNotificationsApi,
} from './alerts.js'; } from './alerts.js';
import { fetchNotifications } from '../alerts.js';
import initLogger from '../logger.js'; import initLogger from '../logger.js';
const logger = initLogger(); const logger = initLogger();
@ -164,6 +166,8 @@ export default async function initServer() {
router.get('/api/tags', fetchTagsApi); router.get('/api/tags', fetchTagsApi);
router.get('*', async (req, res, next) => { router.get('*', async (req, res, next) => {
const notifications = await fetchNotifications(req.user, { limit: 20 });
const pageContextInit = { const pageContextInit = {
urlOriginal: req.originalUrl, urlOriginal: req.originalUrl,
urlQuery: req.query, // vike's own query does not apply boolean parser urlQuery: req.query, // vike's own query does not apply boolean parser
@ -185,6 +189,7 @@ export default async function initServer() {
maxAggregateSize: config.database.manticore.maxAggregateSize, maxAggregateSize: config.database.manticore.maxAggregateSize,
media: config.media, media: config.media,
}, },
notifications,
}; };
const pageContext = await renderPage(pageContextInit); const pageContext = await renderPage(pageContextInit);