Browse Source

Feature/bring notifications back (#801)

* Bring notifications menu back.

* Cleanup some stuff.
pull/802/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
052ee2af4d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs
  2. 1
      backend/i18n/frontend_en.json
  3. 1
      backend/i18n/frontend_it.json
  4. 1
      backend/i18n/frontend_nl.json
  5. 1
      backend/i18n/frontend_zh.json
  6. 1
      backend/i18n/source/frontend_en.json
  7. 11
      backend/src/Squidex/Areas/Api/Controllers/Comments/Models/UpsertCommentDto.cs
  8. 12
      frontend/app/shared/components/comments/comment.component.scss
  9. 29
      frontend/app/shell/pages/internal/notifications-menu.component.html
  10. 13
      frontend/app/shell/pages/internal/notifications-menu.component.scss
  11. 86
      frontend/app/shell/pages/internal/notifications-menu.component.ts

22
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs

@ -33,7 +33,12 @@ namespace Squidex.Extensions.Actions.Notification
{
if (@event is EnrichedUserEventBase userEvent)
{
var text = await FormatAsync(action.Text, @event);
var user = await userResolver.FindByIdOrEmailAsync(action.User);
if (user == null)
{
throw new InvalidOperationException($"Cannot find user by '{action.User}'");
}
var actor = userEvent.Actor;
@ -42,16 +47,13 @@ namespace Squidex.Extensions.Actions.Notification
actor = RefToken.Client(action.Client);
}
var user = await userResolver.FindByIdOrEmailAsync(action.User);
if (user == null)
var ruleJob = new CreateComment
{
throw new InvalidOperationException($"Cannot find user by '{action.User}'");
}
var commentsId = DomainId.Create(user.Id);
var ruleJob = new CreateComment { Actor = actor, CommentsId = commentsId, Text = text };
Actor = actor,
CommentId = DomainId.NewGuid(),
CommentsId = DomainId.Create(user.Id),
Text = await FormatAsync(action.Text, @event)
};
if (!string.IsNullOrWhiteSpace(action.Url))
{

1
backend/i18n/frontend_en.json

@ -607,6 +607,7 @@
"languages.updateFailed": "Failed to change language. Please reload.",
"news.headline": "What's new?",
"news.title": "New Features",
"notifications.empty": "No notifications yet",
"notifo.subscripeTooltip": "Click this button to subscribe to all changes and to receive push notifications.",
"plans.billingPortal": "Billing Portal",
"plans.billingPortalHint": "Go to Billing Portal for payment history and subscription overview.",

1
backend/i18n/frontend_it.json

@ -607,6 +607,7 @@
"languages.updateFailed": "Non è stato possibile cambiare la lingua. Per favore ricarica.",
"news.headline": "Che cosa c'è di nuovo?",
"news.title": "Nuove funzionalità",
"notifications.empty": "No notifications yet",
"notifo.subscripeTooltip": "Fai clic su questo pulsante per iscriverti a tutte le modifiche e ricevere le notifiche push.",
"plans.billingPortal": "Portale di fatturazione",
"plans.billingPortalHint": "Vai al portale di fatturazione per lo storico dei pagamenti e una panoramica per l'abbonamento.",

1
backend/i18n/frontend_nl.json

@ -607,6 +607,7 @@
"languages.updateFailed": "Het wijzigen van de taal is mislukt. Laad opnieuw.",
"news.headline": "Wat is er nieuw?",
"news.title": "Nieuwe functies",
"notifications.empty": "No notifications yet",
"notifo.subscripeTooltip": "Klik op deze knop om je te abonneren op alle wijzigingen en om pushmeldingen te ontvangen.",
"plans.billingPortal": "Factureringsportal",
"plans.billingPortalHint": "Ga naar het factureringsportaal voor betalingsgeschiedenis en abonnementsoverzicht.",

1
backend/i18n/frontend_zh.json

@ -607,6 +607,7 @@
"languages.updateFailed": "更改语言失败。请重新加载。",
"news.headline": "有什么新鲜事?",
"news.title": "新功能",
"notifications.empty": "No notifications yet",
"notifo.subscripeTooltip": "单击此按钮可订阅所有更改并接收推送通知。",
"plans.billingPortal": "计费门户",
"plans.billingPortalHint": "前往账单门户查看付款历史和订阅概览。",

1
backend/i18n/source/frontend_en.json

@ -607,6 +607,7 @@
"languages.updateFailed": "Failed to change language. Please reload.",
"news.headline": "What's new?",
"news.title": "New Features",
"notifications.empty": "No notifications yet",
"notifo.subscripeTooltip": "Click this button to subscribe to all changes and to receive push notifications.",
"plans.billingPortal": "Billing Portal",
"plans.billingPortalHint": "Go to Billing Portal for payment history and subscription overview.",

11
backend/src/Squidex/Areas/Api/Controllers/Comments/Models/UpsertCommentDto.cs

@ -27,12 +27,19 @@ namespace Squidex.Areas.Api.Controllers.Comments.Models
public CreateComment ToCreateCommand(DomainId commentsId)
{
return SimpleMapper.Map(this, new CreateComment { CommentsId = commentsId });
return SimpleMapper.Map(this, new CreateComment
{
CommentsId = commentsId
});
}
public UpdateComment ToUpdateComment(DomainId commentsId, DomainId commentId)
{
return SimpleMapper.Map(this, new UpdateComment { CommentsId = commentsId, CommentId = commentId });
return SimpleMapper.Map(this, new UpdateComment
{
CommentsId = commentsId,
CommentId = commentId
});
}
}
}

12
frontend/app/shared/components/comments/comment.component.scss

@ -1,8 +1,12 @@
/* stylelint-disable no-descending-specificity */
.actions {
@include absolute(-5px, -15px, auto, auto);
background: $color-white;
border: 0;
border-radius: 0;
display: none;
width: auto;
}
.user-ref {
@ -45,6 +49,14 @@
}
}
:host {
&:last-child {
.comment {
margin-bottom: 0;
}
}
}
:host ::ng-deep {
p {
&:last-child {

29
frontend/app/shell/pages/internal/notifications-menu.component.html

@ -1,3 +1,32 @@
<ul class="nav navbar-nav align-items-center">
<sqx-notifo></sqx-notifo>
<ng-container *ngIf="!isNotifoConfigured">
<li class="nav-item nav-icon dropdown position-relative" #button>
<span class="nav-link dropdown-toggle" (click)="modalMenu.show()">
<i class="icon-comments"></i>
<span class="badge rounded-pill bg-danger" *ngIf="unread">{{unread}}</span>
</span>
</li>
<ng-container *sqxModal="modalMenu;onRoot:false">
<div class="dropdown-menu" [scrollTop]="scrollMe.scrollHeight" [sqxAnchoredTo]="button" [offset]="10" @fade #scrollMe>
<ng-container *ngIf="commentsState.comments | async; let comments">
<small class="text-muted" *ngIf="comments.length === 0">
{{ 'notifications.empty' | sqxTranslate}}
</small>
<sqx-comment *ngFor="let comment of comments; trackBy: trackByComment"
[comment]="comment"
[commentsState]="commentsState"
[confirmDelete]="false"
[canDelete]="true"
[canFollow]="true"
[userToken]="userToken">
</sqx-comment>
</ng-container>
</div>
</ng-container>
</ng-container>
</ul>

13
frontend/app/shell/pages/internal/notifications-menu.component.scss

@ -0,0 +1,13 @@
.dropdown-menu {
max-height: 500px;
min-height: 4rem;
overflow-y: scroll;
padding: 1.25rem;
padding-bottom: 1rem;
width: 300px;
}
.badge {
@include absolute(-.5rem, 0, null, null);
font-size: 80%;
}

86
frontend/app/shell/pages/internal/notifications-menu.component.ts

@ -5,13 +5,95 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { timer } from 'rxjs';
import { onErrorResumeNext, switchMap, tap } from 'rxjs/operators';
import { AuthService, CommentDto, CommentsService, CommentsState, DialogService, fadeAnimation, LocalStoreService, ModalModel, ResourceOwner, UIOptions } from '@app/shared';
const CONFIG_KEY = 'notifications.version';
@Component({
selector: 'sqx-notifications-menu',
styleUrls: ['./notifications-menu.component.scss'],
templateUrl: './notifications-menu.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NotificationsMenuComponent {
export class NotificationsMenuComponent extends ResourceOwner implements OnInit {
public modalMenu = new ModalModel();
public commentsState: CommentsState;
public versionRead = -1;
public versionReceived = -1;
public userToken: string;
public get unread() {
return Math.max(0, this.versionReceived - this.versionRead);
}
public isNotifoConfigured: boolean;
constructor(authService: AuthService, commentsService: CommentsService, dialogs: DialogService, uiOptions: UIOptions,
private readonly changeDetector: ChangeDetectorRef,
private readonly localStore: LocalStoreService,
) {
super();
const notifoApiKey = authService.user?.notifoToken;
const notifoApiUrl = uiOptions.get('more.notifoApi');
this.isNotifoConfigured = !!notifoApiKey && !!notifoApiUrl;
this.userToken = authService.user!.token;
this.versionRead = localStore.getInt(CONFIG_KEY, -1);
this.versionReceived = this.versionRead;
const commentsUrl = `users/${authService.user!.id}/notifications`;
this.commentsState =
new CommentsState(
commentsUrl,
commentsService,
dialogs,
true,
this.versionRead);
}
public ngOnInit() {
this.own(
this.modalMenu.isOpenChanges.pipe(
tap(_ => {
this.updateVersion();
}),
));
this.own(
this.commentsState.versionNumber.pipe(
tap(version => {
this.versionReceived = version;
this.updateVersion();
this.changeDetector.detectChanges();
})));
this.own(timer(0, 4000).pipe(switchMap(() => this.commentsState.load(true).pipe(onErrorResumeNext()))));
}
public trackByComment(_index: number, comment: CommentDto) {
return comment.id;
}
private updateVersion() {
if (this.modalMenu.isOpen) {
this.versionRead = this.versionReceived;
this.localStore.setInt(CONFIG_KEY, this.versionRead);
}
}
}

Loading…
Cancel
Save