Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion src/NotificationsApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,34 @@
<template #trigger>
<IconNotification
:size="20"
:showDot="notifications.length !== 0 || webNotificationsGranted === null"
:showDot="!isDnd && (notifications.length !== 0 || webNotificationsGranted === null)"
:showWarning="hasThrottledPushNotifications" />
</template>

<!-- Notifications list content -->
<div class="notification-container">
<!-- Panel header with Do Not Disturb toggle -->
<div class="notification-panel-header">
<span class="notification-panel-title">{{ t('notifications', 'Notifications') }}</span>
<NcButton
variant="tertiary"
:class="{ 'dnd-active': isDnd }"
:title="isDnd ? t('notifications', 'Disable Do Not Disturb') : t('notifications', 'Enable Do Not Disturb')"
:aria-label="isDnd ? t('notifications', 'Disable Do Not Disturb') : t('notifications', 'Enable Do Not Disturb')"
@click="toggleDnd">
<template #icon>
<IconBellOff :size="18" />
</template>
</NcButton>
</div>
<!-- "New while DND" banner -->
<div v-if="dndBanner > 0" class="notification-dnd-banner">
<span>{{ dndBannerText }}</span>
<NcButton variant="tertiary" @click="dndBanner = 0">
<template #icon><IconClose :size="16" /></template>

Check warning on line 42 in src/NotificationsApp.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected 1 line break before closing tag (`</template>`), but no line breaks found

Check warning on line 42 in src/NotificationsApp.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected 1 line break after opening tag (`<template>`), but no line breaks found
</NcButton>
</div>

<transition name="fade" mode="out-in">
<transition-group
v-if="notifications.length > 0"
Expand Down Expand Up @@ -92,6 +114,7 @@
import NcButton from '@nextcloud/vue/components/NcButton'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
import IconBellOff from 'vue-material-design-icons/BellOff.vue'
import IconBellOutline from 'vue-material-design-icons/BellOutline.vue'
import IconClose from 'vue-material-design-icons/Close.vue'
import IconMessageOutline from 'vue-material-design-icons/MessageOutline.vue'
Expand Down Expand Up @@ -131,6 +154,7 @@
name: 'NotificationsApp',

components: {
IconBellOff,
IconBellOutline,
IconClose,
IconMessageOutline,
Expand Down Expand Up @@ -187,6 +211,11 @@
pushEndpoints: null,

open: false,

/** Whether Do Not Disturb is enabled locally (suppresses dot, sounds, browser notifications) */
isDnd: JSON.parse(localStorage.getItem('notifications:dnd') ?? 'false'),
/** Count of notifications that arrived while DND was on (> 0 shows banner) */
dndBanner: 0,
}
},

Expand All @@ -195,9 +224,17 @@
return this.backgroundFetching
&& this.webNotificationsGranted
&& this.userStatus !== 'dnd'
&& !this.isDnd
&& this.tabId === this.lastTabId
},

dndBannerText() {
const c = this.dndBanner
return c === 1
? t('notifications', '1 new notification received while in Do Not Disturb')
: t('notifications', '{count} new notifications received while in Do Not Disturb', { count: c })
},

emptyContentMessage() {
if (this.webNotificationsGranted === null) {
return t('notifications', 'Requesting browser permissions to show notifications')
Expand Down Expand Up @@ -347,6 +384,19 @@
setCurrentTabAsActive(this.tabId)
},

toggleDnd() {
if (!this.isDnd) {
// Turning ON: snapshot current max ID so we can count new arrivals on re-enable
this._dndMaxIdAtEnable = this.notifications.reduce((max, n) => Math.max(max, n.notificationId), 0)
} else {
// Turning OFF: surface a banner for any notifications that arrived while DND was active
const newCount = this.notifications.filter(n => n.notificationId > (this._dndMaxIdAtEnable || 0)).length

Check failure on line 393 in src/NotificationsApp.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected parentheses around arrow function argument
if (newCount > 0) this.dndBanner = newCount

Check failure on line 394 in src/NotificationsApp.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected { after 'if' condition
}
this.isDnd = !this.isDnd
localStorage.setItem('notifications:dnd', JSON.stringify(this.isDnd))
},

/**
* Update the title to show * if there are new notifications
*
Expand Down Expand Up @@ -404,7 +454,7 @@
this._fetch(true)
},

/**

Check warning on line 457 in src/NotificationsApp.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Missing JSDoc @param "force" declaration
* Performs the AJAX request to retrieve the notifications
*/
async _fetch(force = false) {
Expand Down
31 changes: 31 additions & 0 deletions src/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,34 @@ svg {
}
}
}

// Panel header with the Do Not Disturb toggle
.notification-panel-header {
display: flex;
align-items: center;
padding: 2px 4px 2px 16px;
border-bottom: 1px solid var(--color-border);

.notification-panel-title {
flex: 1;
font-size: 13px;
font-weight: 600;
color: var(--color-text-maxcontrast);
}

.dnd-active {
color: var(--color-error) !important;
}
}

// "New while DND" summary banner shown after disabling DND
.notification-dnd-banner {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 4px 4px 16px;
border-bottom: 1px solid var(--color-border);
background: color-mix(in srgb, var(--color-warning) 10%, var(--color-main-background));
font-size: 12px;
color: var(--color-text-maxcontrast);
}
Loading