Added notification bell, WIP.
This commit is contained in:
parent
dc5affb4cf
commit
342ba6191e
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}'.`);
|
||||||
|
|
|
@ -6,5 +6,6 @@ export default {
|
||||||
'urlParsed',
|
'urlParsed',
|
||||||
'env',
|
'env',
|
||||||
'user',
|
'user',
|
||||||
|
'notifications',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue