Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions js/notifications-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/notifications-main.js.map

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,23 @@
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Notification\IManager;
use OCP\Util;

class BeforeTemplateRenderedListener implements IEventListener {
protected IConfig $config;
protected IUserSession $userSession;
protected IInitialState $initialState;
protected IManager $notificationManager;

public function __construct(IConfig $config,
IUserSession $userSession,
IInitialState $initialState) {
IInitialState $initialState,
IManager $notificationManager) {
$this->config = $config;
$this->userSession = $userSession;
$this->initialState = $initialState;
$this->notificationManager = $notificationManager;
}

public function handle(Event $event): void {
Expand Down Expand Up @@ -84,6 +88,16 @@ public function handle(Event $event): void {
) === 'yes'
);

/**
* We want to keep offering our push notification service for free, but large
* users overload our infrastructure. For this reason we have to rate-limit the
* use of push notifications. If you need this feature, consider using Nextcloud Enterprise.
*/
$this->initialState->provideInitialState(
'throttled_push_notifications',
!$this->notificationManager->isFairUseOfFreePushService()
);

Util::addScript('notifications', 'notifications-main');
}
}
60 changes: 41 additions & 19 deletions src/Components/Notification.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<template>
<li class="notification" :data-id="notificationId" :data-timestamp="timestamp">
<div class="notification-heading">
<span v-tooltip.bottom="absoluteDate"
<span v-if="timestamp"
v-tooltip.bottom="absoluteDate"
class="notification-time live-relative-timestamp"
:data-timestamp="timestamp">{{ relativeDate }}</span>
<NcButton class="notification-dismiss-button"
<NcButton v-if="timestamp"
class="notification-dismiss-button"
type="tertiary"
:aria-label="t('notifications', 'Dismiss')"
@click="onDismissNotification">
Expand All @@ -14,7 +16,15 @@
</NcButton>
</div>

<a v-if="useLink" :href="link" class="notification-subject full-subject-link">
<a v-if="externalLink"
:href="externalLink"
class="notification-subject full-subject-link external"
target="_blank"
rel="noreferrer noopener">
<span class="image"><img :src="icon" class="notification-icon" alt=""></span>
<span class="subject">{{ subject }} ↗</span>
</a>
<a v-else-if="useLink" :href="link" class="notification-subject full-subject-link">
<span v-if="icon" class="image"><img :src="icon" class="notification-icon" alt=""></span>
<RichText v-if="subjectRich"
:text="subjectRich"
Expand Down Expand Up @@ -43,6 +53,18 @@
<div v-if="actions.length" class="notification-actions">
<Action v-for="(a, i) in actions" :key="i" v-bind="a" />
</div>
<div v-else-if="externalLink" class="notification-actions">
<NcButton type="primary"
href="https://nextcloud.com/pushnotifications"
class="action-button pull-right"
target="_blank"
rel="noreferrer noopener">
<template #icon>
<Message :size="20" />
</template>
{{ t('notifications', 'Contact Nextcloud GmbH') }} ↗
</NcButton>
</div>
</li>
</template>

Expand All @@ -51,6 +73,7 @@ import axios from '@nextcloud/axios'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
import Close from 'vue-material-design-icons/Close.vue'
import Message from 'vue-material-design-icons/Message.vue'
import { showError } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { Howl } from 'howler'
Expand All @@ -69,6 +92,7 @@ export default {
Action,
NcButton,
Close,
Message,
RichText,
},

Expand All @@ -80,89 +104,77 @@ export default {
notificationId: {
type: Number,
default: -1,
required: true,
},
datetime: {
type: String,
default: '',
required: true,
},
app: {
type: String,
default: '',
required: true,
},
icon: {
type: String,
default: '',
required: true,
},
link: {
type: String,
default: '',
required: true,
},
externalLink: {
type: String,
default: '',
},
user: {
type: String,
default: '',
required: true,
},
message: {
type: String,
default: '',
required: true,
},
messageRich: {
type: String,
default: '',
required: true,
},
messageRichParameters: {
type: [Object, Array],
default() {
return {}
},
required: true,
},
subject: {
type: String,
default: '',
required: true,
},
subjectRich: {
type: String,
default: '',
required: true,
},
subjectRichParameters: {
type: [Object, Array],
default() {
return {}
},
required: true,
},
objectType: {
type: String,
default: '',
required: true,
},
objectId: {
type: String,
default: '',
required: true,
},
actions: {
type: Array,
default() {
return []
},
required: true,
},

index: {
type: Number,
default: -1,
required: true,
},
},

Expand All @@ -176,12 +188,22 @@ export default {

computed: {
timestamp() {
if (this.datetime === 'warning') {
return 0
}
return (new Date(this.datetime)).valueOf()
},
absoluteDate() {
if (this.datetime === 'warning') {
return ''
}
return moment(this.timestamp).format('LLL')
},
relativeDate() {
if (this.datetime === 'warning') {
return ''
}

const diff = moment().diff(moment(this.timestamp))
if (diff >= 0 && diff < 45000) {
return t('core', 'seconds ago')
Expand Down
89 changes: 81 additions & 8 deletions src/NotificationsApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:aria-label="t('notifications', 'Notifications')"
@open="onOpen">
<template #trigger>
<Bell v-if="notifications.length === 0"
<Bell v-if="notifications.length === 0 && webNotificationsGranted !== null && !hasThrottledPushNotifications"
:size="20"
:title="t('notifications', 'Notifications')"
fill-color="var(--color-primary-text)" />
Expand All @@ -22,6 +22,7 @@
fill="var(--color-primary-text)">
<path d="M 19,11.79 C 18.5,11.92 18,12 17.5,12 14.47,12 12,9.53 12,6.5 12,5.03 12.58,3.7 13.5,2.71 13.15,2.28 12.61,2 12,2 10.9,2 10,2.9 10,4 V 4.29 C 7.03,5.17 5,7.9 5,11 v 6 l -2,2 v 1 H 21 V 19 L 19,17 V 11.79 M 12,23 c 1.11,0 2,-0.89 2,-2 h -4 c 0,1.11 0.9,2 2,2 z" />
<path :class="isRedThemed ? 'notification__dot--white' : ''" class="notification__dot" d="M 21,6.5 C 21,8.43 19.43,10 17.5,10 15.57,10 14,8.43 14,6.5 14,4.57 15.57,3 17.5,3 19.43,3 21,4.57 21,6.5" />
<path v-if="hasThrottledPushNotifications" :class="isOrangeThemed ? 'notification__dot--white' : ''" class="notification__dot notification__dot--warning" d="M 21,6.5 C 21,8.43 19.43,10 17.5,10 15.57,10 14,8.43 14,6.5 14,4.57 15.57,3 17.5,3 19.43,3 21,4.57 21,6.5" />
</svg>
</template>

Expand All @@ -32,6 +33,15 @@
<transition-group class="notification-wrapper"
name="list"
tag="ul">
<Notification v-if="hasThrottledPushNotifications"
:key="-2016"
datetime="warning"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datetime=warning ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, its a hack to identify this notification inside the component...

app="core"
:icon="warningIcon"
external-link="https://nextcloud.com/pushnotifications"
:message="emptyContentDescription"
:subject="emptyContentMessage"
:index="2016" />
<Notification v-for="(n, index) in notifications"
:key="n.notificationId"
v-bind="n"
Expand All @@ -55,11 +65,23 @@

<!-- No notifications -->
<NcEmptyContent v-else
:title="webNotificationsGranted === null
? t('notifications', 'Requesting browser permissions to show notifications')
: t('notifications', 'No notifications')">
:title="emptyContentMessage"
:description="emptyContentDescription">
<template #icon>
<Bell />
<Bell v-if="!hasThrottledPushNotifications" />
<span v-else class="icon icon-alert-outline" />
</template>

<template #action>
<NcButton type="primary"
href="https://nextcloud.com/pushnotifications"
target="_blank"
rel="noreferrer noopener">
<template #icon>
<Message :size="20" />
</template>
{{ t('notifications', 'Contact Nextcloud GmbH') }} ↗
</NcButton>
</template>
</NcEmptyContent>
</transition>
Expand All @@ -74,10 +96,15 @@ import Close from 'vue-material-design-icons/Close.vue'
import axios from '@nextcloud/axios'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { showError } from '@nextcloud/dialogs'
import { generateOcsUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import {
generateOcsUrl,
imagePath,
} from '@nextcloud/router'
import { getNotificationsData } from './services/notificationsService.js'
import { listen } from '@nextcloud/notify_push'
import Bell from 'vue-material-design-icons/Bell.vue'
import Message from 'vue-material-design-icons/Message.vue'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import { getCapabilities } from '@nextcloud/capabilities'
import HeaderMenu from './Components/HeaderMenu.vue'
Expand All @@ -89,6 +116,7 @@ export default {
NcButton,
Close,
Bell,
Message,
NcEmptyContent,
HeaderMenu,
Notification,
Expand All @@ -101,6 +129,7 @@ export default {
hasNotifyPush: false,
shutdown: false,
theming: getCapabilities()?.theming || {},
hasThrottledPushNotifications: loadState('notifications', 'throttled_push_notifications'),
notifications: [],
lastETag: null,
lastTabId: null,
Expand Down Expand Up @@ -133,13 +162,47 @@ export default {
}
return false
},
isOrangeThemed() {
if (this.theming?.color) {
const hsl = this.rgbToHsl(this.theming.color.substring(1, 3),
this.theming.color.substring(3, 5),
this.theming.color.substring(5, 7))
const h = hsl[0] * 360
return (h >= 305 || h <= 64) && hsl[1] > 0.7 && (hsl[2] > 0.1 || hsl[2] < 0.6)
}
return false
},

showBrowserNotifications() {
return this.backgroundFetching
&& this.webNotificationsGranted
&& this.userStatus !== 'dnd'
&& this.tabId === this.lastTabId
},

emptyContentMessage() {
if (this.webNotificationsGranted === null) {
return t('notifications', 'Requesting browser permissions to show notifications')
}

if (this.hasThrottledPushNotifications) {
return t('notifications', 'Push notifications might be unreliable')
}

return t('notifications', 'No notifications')
},

emptyContentDescription() {
if (this.hasThrottledPushNotifications) {
return t('notifications', 'Nextcloud GmbH sponsors a free push notification gateway for private users. To ensure good service, the gateway limits the number of push notifications per server. For enterprise users, a more scalable gateway is available. Contact Nextcloud GmbH for more information.')
}

return ''
},

warningIcon() {
return imagePath('core', 'actions/alert-outline.svg')
},
},

mounted() {
Expand Down Expand Up @@ -418,8 +481,18 @@ export default {
overflow: hidden;
}

.empty-content {
margin: 12vh 0;
::v-deep .empty-content {
margin: 12vh 10px;

p {
color: var(--color-text-maxcontrast);
}
}

.icon-alert-outline {
background-size: 64px;
width: 64px;
height: 64px;
}

.fade-enter-active,
Expand Down
Loading