Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notifications rewrite + small fixes #556

Merged
merged 11 commits into from
Nov 19, 2024
137 changes: 0 additions & 137 deletions frontend/src/Notifications.svelte

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/TaskDetail.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SummaryComments from './SummaryComments.svelte';
import SubmitsDiff from './SubmitsDiff.svelte';
import { fetch } from './api.js';
import { user } from './global';
import { notifications } from './notifications.js';
import { notifications } from './utilities/notifications';
import { hideComments, HideCommentsState } from './stores.js';

export let url;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/Teacher/AllTasks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import DataTablesCore from 'datatables.net-bs5';
import DataTable from 'datatables.net-vue3';
import { format } from 'date-fns';
import { onMounted, ref } from 'vue';
import { getFromAPI } from '../utilities';
import { getFromAPI } from '../utilities/api';

DataTable.use(DataTablesCore);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/Teacher/InbusImport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
import { computed, ref } from 'vue';

import { csrfToken } from '../api.js';
import { csrfToken } from '../utilities/api';
import { ConcreteActivity, InbusSubjectVersion } from './inbusdto';

interface KelvinSubject {
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/components/Loader.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<script setup lang="ts">
import { generateRange } from '../utilities';
/**
* Generate array with range of numbers from start
*
* @param size size of the array
* @param start starting number
*
* @returns array of numbers from start to start + size
*/
const generateRange = (size: number, start = 0) => {
return Array.from({ length: size }).map((_, i) => i + start);
};

const size = 60;
const color = '#FF3E00';
Expand All @@ -23,6 +33,7 @@ const unit = 'px';
align-items: center;
justify-content: center;
}

.dot {
height: var(--dotSize);
width: var(--dotSize);
Expand All @@ -32,29 +43,35 @@ const unit = 'px';
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);
}

66% {
-webkit-transform: translateY(-10px);
transform: translateY(-10px);
}

100% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
}

@keyframes sync {
33% {
-webkit-transform: translateY(10px);
transform: translateY(10px);
}

66% {
-webkit-transform: translateY(-10px);
transform: translateY(-10px);
}

100% {
-webkit-transform: translateY(0);
transform: translateY(0);
Expand Down
155 changes: 155 additions & 0 deletions frontend/src/components/Notifications.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<script setup lang="ts">
/**
* This component displays bell with number of notifications next to it.
* After opening, it shows list of all notifications, which user got.
* It is available to both teachers and students, and it is accesible from
* main layout on each page, next to name.
*/

import {
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.'
);
}
}

async function openNotification(notification: Notification) {
if (notification.public) {
await notifications.markRead(notification.id);
}

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();

return sortByImportantOrUnread || sortByDate;
});

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>

<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" />

Check warning on line 111 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 }}

<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>

<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;
}
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/components/SuspensionWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defineProps<{

<template>
<Suspense>
<component :is="childComponent" />
<component :is="childComponent" v-bind="$attrs" />
<template #fallback>
<div class="d-flex justify-content-center loading-animation">
<Loader />
Expand Down
Loading