Skip to content

Commit

Permalink
Reformat
Browse files Browse the repository at this point in the history
  • Loading branch information
patrick11514 committed Nov 12, 2024
1 parent 2aeb067 commit 1e6c653
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 140 deletions.
82 changes: 42 additions & 40 deletions frontend/src/components/Loader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,64 @@ const unit = 'px';
</script>

<template>
<div v-for="i in generateRange(3, 1)" :key="i" class="wrapper" :style="`--size:${size}${unit}`">
<div class="dot"
:style="`--dotSize:${size * 0.25}${unit}; --color:${color}; animation-delay: ${i * 0.07}s;`" />
</div>
<div v-for="i in generateRange(3, 1)" :key="i" class="wrapper" :style="`--size:${size}${unit}`">
<div
class="dot"
:style="`--dotSize:${size * 0.25}${unit}; --color:${color}; animation-delay: ${i * 0.07}s;`"
/>
</div>
</template>

<style scoped>
.wrapper {
height: var(--size);
width: var(--size);
display: flex;
align-items: center;
justify-content: center;
height: var(--size);
width: var(--size);
display: flex;
align-items: center;
justify-content: center;
}
.dot {
height: var(--dotSize);
width: var(--dotSize);
background-color: var(--color);
margin: 2px;
display: inline-block;
border-radius: 100%;
animation: sync 0.6s ease-in-out infinite alternate both running;
height: var(--dotSize);
width: var(--dotSize);
background-color: var(--color);
margin: 2px;
display: inline-block;
border-radius: 100%;
animation: sync 0.6s ease-in-out infinite alternate both running;
}
@-webkit-keyframes sync {
33% {
-webkit-transform: translateY(10px);
transform: translateY(10px);
}
33% {
-webkit-transform: translateY(10px);
transform: translateY(10px);
}
66% {
-webkit-transform: translateY(-10px);
transform: translateY(-10px);
}
66% {
-webkit-transform: translateY(-10px);
transform: translateY(-10px);
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
}
@keyframes sync {
33% {
-webkit-transform: translateY(10px);
transform: translateY(10px);
}
33% {
-webkit-transform: translateY(10px);
transform: translateY(10px);
}
66% {
-webkit-transform: translateY(-10px);
transform: translateY(-10px);
}
66% {
-webkit-transform: translateY(-10px);
transform: translateY(-10px);
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
}
</style>
204 changes: 116 additions & 88 deletions frontend/src/components/Notifications.vue
Original file line number Diff line number Diff line change
@@ -1,120 +1,148 @@
<script setup lang="ts">
import {
notifications,
pushNotifications,
importantNotificationsCount,
notificationsCount,
type Notification
notifications,
pushNotifications,
importantNotificationsCount,
notificationsCount,
type Notification
} from '../utilities/notifications';
import TimeAgo from './TimeAgo.vue';
async function enablePushNotifications() {
if (!(await pushNotifications.subscribePushNotifications())) {
alert(
'Notifications are denied, click on the icon before the URL address and enable them manually.\n\nAlso check if option "Use Google services for push messaging" is enabled in your browser privacy settings.'
);
}
if (!(await pushNotifications.subscribePushNotifications())) {
alert(
'Notifications are denied, click on the icon before the URL address and enable them manually.\n\nAlso check if option "Use Google services for push messaging" is enabled in your browser privacy settings.'
);
}
}
async function openNotification(notification: Notification) {
if (notification.public) {
await notifications.markRead(notification.id);
}
if (notification.public) {
await notifications.markRead(notification.id);
}
document.location.href = notification.action_object_url;
document.location.href = notification.action_object_url;
}
const getFilteredNotifications = (notifications: Readonly<Notification[]>) => {
const ret = notifications.slice();
ret.sort((a, b) => {
const sortByImportantOrUnread =
Number((b.important || 0) && b.unread) - Number((a.important || 0) && a.unread);
const sortByDate = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
const ret = notifications.slice();
ret.sort((a, b) => {
const sortByImportantOrUnread =
Number((b.important || 0) && b.unread) - Number((a.important || 0) && a.unread);
const sortByDate = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
return sortByImportantOrUnread || sortByDate;
});
return sortByImportantOrUnread || sortByDate;
});
return ret;
return ret;
};
</script>

<template>
<li v-if="notifications" class="nav-item dropdown">
<button class="btn nav-link dropdown-toggle" href="#" type="button" data-bs-toggle="dropdown"
data-bs-auto-close="outside" aria-expanded="false" title="Notifications">
<span class="iconify" data-icon="bi:bell"></span>
<span class="d-md-none ms-1">Notifications</span>
<span v-if="notificationsCount > 0" :class="`badge ${importantNotificationsCount >= 1 ? 'text-bg-danger' : 'text-bg-warning'
} border border-light rounded-pill`">
{{ notificationsCount }}
<span class="visually-hidden">New alerts</span>
</span>
</button>
<li v-if="notifications" class="nav-item dropdown">
<button
class="btn nav-link dropdown-toggle"
href="#"
type="button"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
title="Notifications"
>
<span class="iconify" data-icon="bi:bell"></span>
<span class="d-md-none ms-1">Notifications</span>
<span
v-if="notificationsCount > 0"
:class="`badge ${
importantNotificationsCount >= 1 ? 'text-bg-danger' : 'text-bg-warning'
} border border-light rounded-pill`"
>
{{ notificationsCount }}
<span class="visually-hidden">New alerts</span>
</span>
</button>

<div class="dropdown-menu dropdown-menu-end shadow p-0 rounded dropdown-menu-custom">
<ul class="list-group list-group-flush" style="max-height: 50vh; overflow-y: auto;">
<li class="list-group-item">
<div class="d-flex align-items-center">
<div>
Notifications<template v-if="notificationsCount > 0">
&nbsp;({{ notificationsCount }})
</template>
</div>
<div class="dropdown-menu dropdown-menu-end shadow p-0 rounded dropdown-menu-custom">
<ul class="list-group list-group-flush" style="max-height: 50vh; overflow-y: auto">
<li class="list-group-item">
<div class="d-flex align-items-center">
<div>
Notifications<template v-if="notificationsCount > 0">
&nbsp;({{ notificationsCount }})
</template>
</div>

<div class="ms-auto">
<button v-if="pushNotifications.ref.value.supported && !pushNotifications.ref.value.enabled"
class="btn text-body" title="Enable desktop notifications"
@click="enablePushNotifications">
<span class="iconify" data-icon="ic:outline-notifications-active"></span>
</button>
<button class="btn text-body" :class="{ 'text-muted': notificationsCount <= 0 }"
title="Clear all notifications" @click="notifications.markAllRead">
<span class="iconify" data-icon="mdi:notification-clear-all"></span>
</button>
</div>
</div>
</li>
<template v-if="notifications.notificationsRef.value.length > 0">
<li v-for="item in getFilteredNotifications(notifications.notificationsRef.value)" :key="item.id"
class="list-group-item p-1 d-flex align-items-center justify-content-between"
:class="{ 'text-body-secondary': !item.unread || !item.important }">
<div>
<strong>{{ item.actor }}&nbsp;</strong>
<div v-if="item.custom_text" v-html="item.custom_text" />
<div class="ms-auto">
<button
v-if="pushNotifications.ref.value.supported && !pushNotifications.ref.value.enabled"
class="btn text-body"
title="Enable desktop notifications"
@click="enablePushNotifications"
>
<span class="iconify" data-icon="ic:outline-notifications-active"></span>
</button>
<button
class="btn text-body"
:class="{ 'text-muted': notificationsCount <= 0 }"
title="Clear all notifications"
@click="notifications.markAllRead"
>
<span class="iconify" data-icon="mdi:notification-clear-all"></span>
</button>
</div>
</div>
</li>
<template v-if="notifications.notificationsRef.value.length > 0">
<li
v-for="item in getFilteredNotifications(notifications.notificationsRef.value)"
:key="item.id"
class="list-group-item p-1 d-flex align-items-center justify-content-between"
:class="{ 'text-body-secondary': !item.unread || !item.important }"
>
<div>
<strong>{{ item.actor }}&nbsp;</strong>
<div v-if="item.custom_text" v-html="item.custom_text" />

Check warning on line 104 in frontend/src/components/Notifications.vue

View workflow job for this annotation

GitHub Actions / test_frontend

'v-html' directive can lead to XSS attack

<template v-else>
{{ item.verb }}
<template v-else>
{{ item.verb }}

<a v-if="item.action_object_url" :href="item.action_object_url"
@click.prevent="openNotification(item)" @auxclick="notifications.markRead(item.id)">
{{ item.action_object }}
</a>
<template v-else>{item.action_object}</template>
<a
v-if="item.action_object_url"
:href="item.action_object_url"
@click.prevent="openNotification(item)"
@auxclick="notifications.markRead(item.id)"
>
{{ item.action_object }}
</a>
<template v-else>{item.action_object}</template>

<template v-if="item.target"> on {{ item.target }}</template>
</template>
<span>&nbsp;(
<TimeAgo :datetime="item.timestamp" />)
</span>
</div>
<template v-if="item.target"> on {{ item.target }}</template>
</template>
<span>&nbsp;(<TimeAgo :datetime="item.timestamp" />) </span>
</div>

<div>
<button type="button" :hidden="!item.unread" class="btn-close" aria-label="Close"
@click="notifications.markRead(item.id)"></button>
</div>
</li>
</template>
<span v-else class="list-group-item p-1 text-center">There are no notifications!</span>
</ul>
</div>
</li>
<div>
<button
type="button"
:hidden="!item.unread"
class="btn-close"
aria-label="Close"
@click="notifications.markRead(item.id)"
></button>
</div>
</li>
</template>
<span v-else class="list-group-item p-1 text-center">There are no notifications!</span>
</ul>
</div>
</li>
</template>

<style>
/* 768px - md (medium) bootstrap breakpoint; when the navbar collapses, this gets disabled */
@media (min-width: 768px) {
.dropdown-menu-custom {
min-width: 26rem;
}
.dropdown-menu-custom {
min-width: 26rem;
}
}
</style>
18 changes: 9 additions & 9 deletions frontend/src/components/SuspensionWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
import Loader from './Loader.vue';
defineProps<{
childComponent: object;
childComponent: object;
}>();
</script>

<template>
<Suspense>
<component :is="childComponent" v-bind="$attrs" />
<template #fallback>
<div class="d-flex justify-content-center loading-animation">
<Loader />
</div>
</template>
</Suspense>
<Suspense>
<component :is="childComponent" v-bind="$attrs" />
<template #fallback>
<div class="d-flex justify-content-center loading-animation">
<Loader />
</div>
</template>
</Suspense>
</template>
6 changes: 3 additions & 3 deletions frontend/src/utilities/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { computed } from '@vue/reactivity';
import { computed } from 'vue';
import { ref } from 'vue';
import { getDataWithCSRF } from './utils';

Expand All @@ -24,7 +24,7 @@ export type Notification = {
target?: string;
};

export const notifications = (function() {
export const notifications = (function () {
const notificationsRef = ref<Notification[]>([]);

const refresh = async () => {
Expand Down Expand Up @@ -53,7 +53,7 @@ export const notifications = (function() {
};
})();

export const pushNotifications = (function() {
export const pushNotifications = (function () {
const pushNotificationsStatus = ref({
supported: false,
enabled: null
Expand Down
Loading

0 comments on commit 1e6c653

Please sign in to comment.