From e9c1283c2ddc089a212646f135fb8c6b8e3ca797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20So=C5=9Bnierz?= Date: Mon, 19 Oct 2020 18:28:49 +0200 Subject: [PATCH 1/4] feat(filters): Add filters page --- e2e/mockserver/mockserver.ts | 73 +++++++++ src/app/account-app/account-app.module.ts | 8 + .../filters/account-filters.component.html | 25 +++ .../filters/account-filters.component.ts | 146 ++++++++++++++++++ .../filters/filter-editor.component.html | 86 +++++++++++ .../filters/filter-editor.component.ts | 89 +++++++++++ src/app/menu/headertoolbar.component.html | 2 +- src/app/rmmapi/rbwebmail.ts | 39 +++++ 8 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 src/app/account-app/filters/account-filters.component.html create mode 100644 src/app/account-app/filters/account-filters.component.ts create mode 100644 src/app/account-app/filters/filter-editor.component.html create mode 100644 src/app/account-app/filters/filter-editor.component.ts diff --git a/e2e/mockserver/mockserver.ts b/e2e/mockserver/mockserver.ts index 3285ec46b..6a760bed7 100644 --- a/e2e/mockserver/mockserver.ts +++ b/e2e/mockserver/mockserver.ts @@ -269,6 +269,9 @@ export class MockServer { case '/rest/v1/me': response.end(JSON.stringify(this.me())); break; + case '/rest/v1/filter': + response.end(JSON.stringify(this.filters())); + break; case '/rest/v1/list/deleted_messages': response.end(JSON.stringify({ 'message_ids': [], 'status': 'success' })); break; @@ -691,4 +694,74 @@ export class MockServer { ], ]; } + + filters(): any { + return { + "result": { + "filters": [ + { + "active": true, + "action": "t", + "id": 101486365, + "priority": 0, + "location": "1", + "target": "Inbox", + "negated": false, + "string": "from-rule" + }, + { + "string": "reply-to-rule", + "active": true, + "action": "t", + "id": 101486367, + "priority": 0, + "location": "4", + "target": "Inbox", + "negated": false + }, + { + "negated": false, + "target": "Inbox", + "location": "0", + "priority": 0, + "id": 101486369, + "action": "t", + "active": true, + "string": "to-rule" + }, + { + "string": "from-forwarded", + "negated": false, + "priority": 0, + "location": "1", + "target": "target@runbox.com", + "action": "f", + "id": 101486371, + "active": true + }, + { + "string": "cc-redirected", + "active": true, + "action": "b", + "id": 101486373, + "priority": 0, + "location": "3", + "target": "target@runbox.com", + "negated": false + }, + { + "string": "added-in-rmm7", + "active": true, + "action": "t", + "id": 101486379, + "location": "0", + "priority": null, + "target": "Inbox", + "negated": false + } + ] + }, + "status": "success" + } + } } diff --git a/src/app/account-app/account-app.module.ts b/src/app/account-app/account-app.module.ts index 513ed9c9d..308fb309b 100644 --- a/src/app/account-app/account-app.module.ts +++ b/src/app/account-app/account-app.module.ts @@ -31,6 +31,8 @@ import { HeaderToolbarComponent } from '../menu/headertoolbar.component'; import { AccountAppComponent } from './account-app.component'; import { AccountAddonsComponent } from './account-addons.component'; import { AccountComponentsComponent } from './account-components.component'; +import { AccountFiltersComponent } from './filters/account-filters.component'; +import { FilterEditorComponent } from './filters/filter-editor.component'; import { AccountRenewalsComponent } from './account-renewals.component'; import { AccountReceiptComponent } from './account-receipt.component'; import { AccountTransactionsComponent } from './account-transactions.component'; @@ -74,6 +76,7 @@ import { SubAccountRenewalDialogComponent } from './sub-account-renewal-dialog'; AccountAddonsComponent, AccountAppComponent, AccountComponentsComponent, + AccountFiltersComponent, AccountReceiptComponent, AccountRenewalsComponent, AccountTransactionsComponent, @@ -90,6 +93,7 @@ import { SubAccountRenewalDialogComponent } from './sub-account-renewal-dialog'; SubAccountRenewalDialogComponent, RunboxTimerComponent, CreditCardsComponent, + FilterEditorComponent, ], imports: [ BrowserAnimationsModule, @@ -173,6 +177,10 @@ import { SubAccountRenewalDialogComponent } from './sub-account-renewal-dialog'; path: 'credit_cards', component: CreditCardsComponent }, + { + path: 'filters', + component: AccountFiltersComponent + }, ] } ] diff --git a/src/app/account-app/filters/account-filters.component.html b/src/app/account-app/filters/account-filters.component.html new file mode 100644 index 000000000..51b07e82e --- /dev/null +++ b/src/app/account-app/filters/account-filters.component.html @@ -0,0 +1,25 @@ +

Filters

+ +
+
+ +
+ + +
+ + + + diff --git a/src/app/account-app/filters/account-filters.component.ts b/src/app/account-app/filters/account-filters.component.ts new file mode 100644 index 000000000..f06fa0a8e --- /dev/null +++ b/src/app/account-app/filters/account-filters.component.ts @@ -0,0 +1,146 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2020 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { Component, QueryList, ViewChildren } from '@angular/core'; +import { ReplaySubject, Subject } from 'rxjs'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +import { Filter, RunboxWebmailAPI } from '../../rmmapi/rbwebmail'; +import { take, debounceTime } from 'rxjs/operators'; +import { FilterEditorComponent } from './filter-editor.component'; + +@Component({ + selector: 'app-account-filters-component', + templateUrl: './account-filters.component.html', +}) +export class AccountFiltersComponent { + @ViewChildren(FilterEditorComponent) filterComponents: QueryList; + filters: ReplaySubject = new ReplaySubject(1); + filtersReordered: Subject = new Subject(); + + constructor( + private rmmapi: RunboxWebmailAPI, + private snackbar: MatSnackBar, + ) { + this.rmmapi.getFilters().subscribe(filters => { + this.filters.next(filters); + }); + + this.filtersReordered.pipe(debounceTime(1500)).subscribe(() => { + this.filters.pipe(take(1)).subscribe(filters => { + const order = filters.map(f => f.id); + this.rmmapi.reorderFilters(order).subscribe(() => { + console.log('Filters reordered'); + }); + }); + }); + } + + newFilter(): void { + const template = { + id: null, + str: '', + action: 't', + active: true, + target: 'Inbox', + negated: false, + location: '0', + priority: -1, + }; + this.updateFilters( + filters => [template, ...filters] + ); + } + + deleteFilter(target: Filter): void { + if (target.id) { + console.log(`Deleting filter #${target.id}`); + this.rmmapi.deleteFilter(target).subscribe( + () => console.log(`Filter #${target.id} deleted`), + ); + } + this.updateFilters( + filters => filters.filter(f => f !== target) + ); + } + + saveFilter(existing: Filter, replacement: Filter): void { + console.log(`Uploading filter to server ${JSON.stringify(replacement)}`); + this.rmmapi.saveFilter(replacement).subscribe( + id => { + replacement.id = id; // only needed when a new one is created, but no difference to us + this.updateFilters( + filters => filters.map(f => { + if (f === existing) { + return replacement; + } else { + return f; + } + }) + ); + }, + _err => { + const action = existing.id ? 'updating' : 'creating'; + const message = `Error ${action} filter. Try again or contact Runbox Support`; + this.snackbar.open(message, 'Ok', { + duration: 3000, + }); + }, + ); + } + + updateFilters(transform: (_: Filter[]) => Filter[]): void { + this.filters.pipe(take(1)).subscribe( + filters => this.filters.next(transform(filters)) + ); + } + + moveFilterUp(filter: Filter): void { + this.updateFilters(filters => { + const index = filters.findIndex(f => f === filter); + if (index === 0) { + return filters; + } + const head = filters.slice(0, index); + let tail = filters.slice(index + 1); + tail = [head.pop(), ...tail]; + setTimeout(() => this.hilightFilter(filter), 50); + return [...head, filter, ...tail]; + }); + this.filtersReordered.next(); + } + + moveFilterDown(filter: Filter): void { + this.updateFilters(filters => { + const index = filters.findIndex(f => f === filter); + if (index === filters.length - 1) { + return filters; + } + const head = filters.slice(0, index); + const tail = filters.slice(index + 1); + setTimeout(() => this.hilightFilter(filter), 50); + return [...head, tail.shift(), filter, ...tail]; + }); + this.filtersReordered.next(); + } + + hilightFilter(filter: Filter): void { + this.filterComponents.find(fc => fc.filter === filter).hilight(); + } +} diff --git a/src/app/account-app/filters/filter-editor.component.html b/src/app/account-app/filters/filter-editor.component.html new file mode 100644 index 000000000..71602be48 --- /dev/null +++ b/src/app/account-app/filters/filter-editor.component.html @@ -0,0 +1,86 @@ + + + +
+
+ Active + + +
+ + A message where + + To + From + Subject + Cc + Reply-To + Body + Return-Path + Delivered-To + Mailing-List + Header + Address-suffix + List-ID + + +
+ + + doesn't contain + contains + + + + +
+ + Will be + + moved to folder + forwarded to + redirected to + + + + Select folder + + {{ folder }} + + + + + Target + + + +
+
+ + + + + + +
diff --git a/src/app/account-app/filters/filter-editor.component.ts b/src/app/account-app/filters/filter-editor.component.ts new file mode 100644 index 000000000..bd8d1aa1e --- /dev/null +++ b/src/app/account-app/filters/filter-editor.component.ts @@ -0,0 +1,89 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2019 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { Component, Input, OnInit, Output, EventEmitter, ViewChild, ElementRef, Renderer2 } from '@angular/core'; +import { Filter } from '../../rmmapi/rbwebmail'; +import { MessageListService } from '../../rmmapi/messagelist.service'; +import { FormGroup, FormBuilder } from '@angular/forms'; + +@Component({ + selector: 'app-account-filter-editor', + templateUrl: './filter-editor.component.html', +}) +export class FilterEditorComponent implements OnInit { + @ViewChild('cardComponent', { read: ElementRef }) cardComponent: ElementRef; + @Input() filter: Filter; + + @Output() delete: EventEmitter = new EventEmitter(); + @Output() save: EventEmitter = new EventEmitter(); + @Output() moveUp: EventEmitter = new EventEmitter(); + @Output() moveDown: EventEmitter = new EventEmitter(); + + isNegated: boolean; + folders: string[] = []; + form: FormGroup; + + constructor( + private fb: FormBuilder, + private renderer: Renderer2, + messageListService: MessageListService, + ) { + messageListService.folderListSubject.subscribe(folders => { + this.folders = folders.map(f => f.folderPath); + }); + } + + negate(): void { + this.isNegated = !this.isNegated; + this.form.get('negated').setValue(this.isNegated); + this.form.get('negated').markAsDirty(); + } + + ngOnInit() { + this.reloadForm(); + } + + reloadForm(): void { + this.form = this.fb.group({ + active: this.fb.control(this.filter.active), + location: this.fb.control(this.filter.location), + negated: this.fb.control(this.filter.negated), + str: this.fb.control(this.filter.str), + action: this.fb.control(this.filter.action), + target: this.fb.control(this.filter.target), + }); + this.isNegated = this.filter.negated; + } + + deleteFilter(): void { + this.delete.emit(); + } + + saveFilter(): void { + const newFilter = {id: this.filter.id}; + Object.assign(newFilter, this.form.value); + this.save.emit(newFilter as Filter); + } + + hilight(): void { + this.renderer.addClass(this.cardComponent.nativeElement, 'hilight'); + this.cardComponent.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + setTimeout(() => this.renderer.removeClass(this.cardComponent.nativeElement, 'hilight'), 250); + } +} diff --git a/src/app/menu/headertoolbar.component.html b/src/app/menu/headertoolbar.component.html index 25763ec76..7007f6941 100644 --- a/src/app/menu/headertoolbar.component.html +++ b/src/app/menu/headertoolbar.component.html @@ -18,7 +18,7 @@ Manager
Retrieve
- Filter
+ Filter
Access
+ +

diff --git a/src/app/account-app/filters/account-filters.component.ts b/src/app/account-app/filters/account-filters.component.ts index f06fa0a8e..3d54d864f 100644 --- a/src/app/account-app/filters/account-filters.component.ts +++ b/src/app/account-app/filters/account-filters.component.ts @@ -32,8 +32,13 @@ import { FilterEditorComponent } from './filter-editor.component'; export class AccountFiltersComponent { @ViewChildren(FilterEditorComponent) filterComponents: QueryList; filters: ReplaySubject = new ReplaySubject(1); + shownFilters: Subject = new Subject(); filtersReordered: Subject = new Subject(); + filterPageSize = 50; + filtersShown = this.filterPageSize; + filtersTotal: number; + constructor( private rmmapi: RunboxWebmailAPI, private snackbar: MatSnackBar, @@ -42,6 +47,8 @@ export class AccountFiltersComponent { this.filters.next(filters); }); + this.filters.subscribe(_ => this.updateShownFilters()); + this.filtersReordered.pipe(debounceTime(1500)).subscribe(() => { this.filters.pipe(take(1)).subscribe(filters => { const order = filters.map(f => f.id); @@ -143,4 +150,21 @@ export class AccountFiltersComponent { hilightFilter(filter: Filter): void { this.filterComponents.find(fc => fc.filter === filter).hilight(); } + + showAllFilters(): void { + this.filtersShown = Number.MAX_SAFE_INTEGER; + this.updateShownFilters(); + } + + showMoreFilters(): void { + this.filtersShown += this.filterPageSize; + this.updateShownFilters(); + } + + updateShownFilters(): void { + this.filters.pipe(take(1)).subscribe(filters => { + this.shownFilters.next(filters.slice(0, this.filtersShown)); + this.filtersTotal = filters.length; + }); + } } From 5f930d3a323d8cb1ce243b6a0e2076c32b5fe231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20So=C5=9Bnierz?= Date: Fri, 20 Nov 2020 17:07:42 +0100 Subject: [PATCH 4/4] feat(filters): Add tabs for allowed and blocked senders --- src/app/account-app/account-app.module.ts | 4 + .../filters/account-filters.component.html | 64 ++++++++----- .../filters/account-filters.component.ts | 90 ++++++++++++++++--- .../filters/sender-list.component.html | 36 ++++++++ .../filters/sender-list.component.ts | 43 +++++++++ src/app/rmmapi/rbwebmail.ts | 45 ++++++++-- 6 files changed, 243 insertions(+), 39 deletions(-) create mode 100644 src/app/account-app/filters/sender-list.component.html create mode 100644 src/app/account-app/filters/sender-list.component.ts diff --git a/src/app/account-app/account-app.module.ts b/src/app/account-app/account-app.module.ts index 308fb309b..a14063039 100644 --- a/src/app/account-app/account-app.module.ts +++ b/src/app/account-app/account-app.module.ts @@ -33,6 +33,7 @@ import { AccountAddonsComponent } from './account-addons.component'; import { AccountComponentsComponent } from './account-components.component'; import { AccountFiltersComponent } from './filters/account-filters.component'; import { FilterEditorComponent } from './filters/filter-editor.component'; +import { SenderListComponent } from './filters/sender-list.component'; import { AccountRenewalsComponent } from './account-renewals.component'; import { AccountReceiptComponent } from './account-receipt.component'; import { AccountTransactionsComponent } from './account-transactions.component'; @@ -65,6 +66,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatTabsModule } from '@angular/material/tabs'; import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -94,6 +96,7 @@ import { SubAccountRenewalDialogComponent } from './sub-account-renewal-dialog'; RunboxTimerComponent, CreditCardsComponent, FilterEditorComponent, + SenderListComponent, ], imports: [ BrowserAnimationsModule, @@ -115,6 +118,7 @@ import { SubAccountRenewalDialogComponent } from './sub-account-renewal-dialog'; MatRadioModule, MatSelectModule, MatSidenavModule, + MatTabsModule, MatTableModule, MatToolbarModule, MatTooltipModule, diff --git a/src/app/account-app/filters/account-filters.component.html b/src/app/account-app/filters/account-filters.component.html index f91234769..bad3961ac 100644 --- a/src/app/account-app/filters/account-filters.component.html +++ b/src/app/account-app/filters/account-filters.component.html @@ -1,28 +1,50 @@

Filters

-
-
- -
+ + +
+
+ +
- + -

- Showing {{ filters.length }}/{{ filtersTotal }} filters - - -

-
+

+ Showing {{ filters.length }}/{{ filtersTotal }} filters + + +

+
+ + +
+ +
+
+ +
+ +
+
+ ; filters: ReplaySubject = new ReplaySubject(1); shownFilters: Subject = new Subject(); + blockedSenders: ReplaySubject = new ReplaySubject(1); + allowedSenders: ReplaySubject = new ReplaySubject(1); + filtersReordered: Subject = new Subject(); filterPageSize = 50; @@ -44,7 +47,9 @@ export class AccountFiltersComponent { private snackbar: MatSnackBar, ) { this.rmmapi.getFilters().subscribe(filters => { - this.filters.next(filters); + this.filters.next(filters.filters); + this.allowedSenders.next(filters.allowed); + this.blockedSenders.next(filters.blocked); }); this.filters.subscribe(_ => this.updateShownFilters()); @@ -78,7 +83,7 @@ export class AccountFiltersComponent { deleteFilter(target: Filter): void { if (target.id) { console.log(`Deleting filter #${target.id}`); - this.rmmapi.deleteFilter(target).subscribe( + this.rmmapi.deleteFilter(target.id).subscribe( () => console.log(`Filter #${target.id} deleted`), ); } @@ -102,13 +107,7 @@ export class AccountFiltersComponent { }) ); }, - _err => { - const action = existing.id ? 'updating' : 'creating'; - const message = `Error ${action} filter. Try again or contact Runbox Support`; - this.snackbar.open(message, 'Ok', { - duration: 3000, - }); - }, + _err => this.showError(`Error ${existing.id ? 'updating' : 'creating'} filter.`), ); } @@ -167,4 +166,75 @@ export class AccountFiltersComponent { this.filtersTotal = filters.length; }); } + + addAllowed(address: string): void { + this.rmmapi.whitelistSender(address).subscribe( + () => { + this.allowedSenders.pipe(take(1)).subscribe(allowed => { + this.allowedSenders.next( + allowed.concat({ + id: address, + address: address, + }) + ); + }); + }, + _err => this.showError('Error whitelisting an address.'), + ); + } + + removeAllowed(id: any): void { + this.rmmapi.dewhitelistSender(id).subscribe( + () => { + this.allowedSenders.pipe(take(1)).subscribe(allowed => { + this.allowedSenders.next( + allowed.filter(s => s.id !== id) + ); + }); + }, + _err => this.showError('Error dewhitelisting an address.'), + ); + } + + addBlocked(address: string): void { + const filter = { + id: null, + str: address, + action: 'k', + active: true, + target: '', + negated: false, + location: '1', + priority: -2, + }; + this.rmmapi.saveFilter(filter).subscribe( + id => { + this.blockedSenders.pipe(take(1)).subscribe(blocked => { + this.blockedSenders.next( + blocked.concat({ id, address }) + ); + }); + }, + _err => this.showError('Error blocking an address.'), + ); + } + + removeBlocked(id: any): void { + this.rmmapi.deleteFilter(id).subscribe( + () => { + this.blockedSenders.pipe(take(1)).subscribe(blocked => { + this.blockedSenders.next( + blocked.filter(f => f.id !== id) + ); + }); + }, + _err => this.showError('Error unblocking an address.'), + ); + } + + showError(message: string): void { + this.snackbar.open(message + ' Try again or contact Runbox Support', 'Ok', { + duration: 3000, + }); + } } diff --git a/src/app/account-app/filters/sender-list.component.html b/src/app/account-app/filters/sender-list.component.html new file mode 100644 index 000000000..b85c5805f --- /dev/null +++ b/src/app/account-app/filters/sender-list.component.html @@ -0,0 +1,36 @@ + + + + {{ sender.address }} + + + + Add new + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/account-app/filters/sender-list.component.ts b/src/app/account-app/filters/sender-list.component.ts new file mode 100644 index 000000000..5334a3f59 --- /dev/null +++ b/src/app/account-app/filters/sender-list.component.ts @@ -0,0 +1,43 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2020 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { Component, Input, OnChanges, Output, EventEmitter } from '@angular/core'; + +import { FilteredSender } from '../../rmmapi/rbwebmail'; + +@Component({ + selector: 'app-account-sender-list', + templateUrl: './sender-list.component.html', +}) +export class SenderListComponent implements OnChanges { + @Input() senders: FilteredSender[]; + + @Output() add: EventEmitter = new EventEmitter(); + @Output() remove: EventEmitter = new EventEmitter(); + + senderList: FilteredSender[]; + + ngOnChanges() { + // adding a placeholder for the input field + this.senderList = this.senders.concat({ + id: null, + address: '', + }); + } +} diff --git a/src/app/rmmapi/rbwebmail.ts b/src/app/rmmapi/rbwebmail.ts index 5b88f240d..8db8545f1 100644 --- a/src/app/rmmapi/rbwebmail.ts +++ b/src/app/rmmapi/rbwebmail.ts @@ -84,6 +84,17 @@ export class ContactSyncResult { ) { } } +export interface AccountFilters { + filters: Filter[]; + blocked: FilteredSender[]; + allowed: FilteredSender[]; +} + +export interface FilteredSender { + id: any; + address: string; +} + export interface Filter { id: number; active: boolean; @@ -798,13 +809,19 @@ export class RunboxWebmailAPI { ); } - getFilters(): Observable { + getFilters(): Observable { return this.http.get('/rest/v1/filter').pipe( - map((res: HttpResponse) => res['result']['filters'].map((entry: any) => { - entry['str'] = entry['string']; - delete entry['string']; - return entry; - })) + map((res: HttpResponse) => { + return { + filters: res['result']['filters'].map((entry: any) => { + entry['str'] = entry['string']; + delete entry['string']; + return entry; + }), + allowed: res['result']['allowed'], + blocked: res['result']['blocked'], + }; + }) ); } @@ -814,8 +831,8 @@ export class RunboxWebmailAPI { ); } - deleteFilter(f: Filter): Observable { - return this.http.delete('/rest/v1/filter/' + f.id).pipe( + deleteFilter(id: number): Observable { + return this.http.delete('/rest/v1/filter/' + id).pipe( map((res: HttpResponse) => res['result']) ); } @@ -825,4 +842,16 @@ export class RunboxWebmailAPI { map((res: HttpResponse) => res['result']) ); } + + whitelistSender(address: string): Observable { + return this.http.put('/rest/v1/filter/whitelist/' + address, {}).pipe( + map((res: HttpResponse) => res['result']) + ); + } + + dewhitelistSender(address: string): Observable { + return this.http.delete('/rest/v1/filter/whitelist/' + address, {}).pipe( + map((res: HttpResponse) => res['result']) + ); + } }