diff --git a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.html b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.html index 7b52aea060be2052e558992e4682f0bc588f1ea8..bde177ecc825c95bfcd3acb342d8d6ebea804059 100644 --- a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.html +++ b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.html @@ -3,6 +3,15 @@ [ngClass]="{'search-container' : !scroll, 'search-container-fixed' : scroll}" (window:scroll)="checkScroll()" fxLayoutAlign="center"> + + <button id="filter-close-button" + mat-icon-button + class="searchBarButton" + *ngIf="filter.filterType" + (click)="applyFilterByKey(null)" + aria-labelledby="close_filter"> + <mat-icon class="searchBarIcon red">close</mat-icon> + </button> <mat-label *ngIf="deviceType === 'desktop'" fxLayoutAlign="center center"> <mat-icon class="search-icon">search</mat-icon> @@ -13,14 +22,14 @@ [ngClass]="{'desktop-input': deviceType === 'desktop', 'mobile-input': deviceType === 'mobile' && !search, 'mobile-input-2': deviceType === 'mobile' && search }" (input)="searchComments()" - [(ngModel)]="searchInput" + [(ngModel)]="filter.currentSearch" [placeholder]="searchPlaceholder" aria-labelledby="search-box-input-description"/> <button id="search_close-button" mat-icon-button class="searchBarButton close red" - *ngIf="searchInput !== '' || search" - (click)="hideCommentsList=false; searchInput = ''; search = false; searchPlaceholder = '';"> + *ngIf="filter.currentSearch || search" + (click)="abortSearch()"> <mat-icon>close</mat-icon> </button> @@ -55,7 +64,7 @@ aria-labelledby="time_settings" *ngIf="!searchBox.value && comments && comments.length > 0 && !search" [matMenuTriggerFor]="timeMenu" - [ngClass]="{'active-filter': period !== 'time-all'}" + [ngClass]="{'active-filter': filter.period !== 'time-all'}" matTooltip="{{ 'comment-list.select-time' | translate }}"> <mat-icon class="searchBarIcon">history</mat-icon> </button> @@ -73,25 +82,25 @@ xPosition="before"> <button mat-menu-item matTooltip="{{ 'comment-list.time' | translate }}" - (click)="sortComments(time)" + (click)="applySortingByKey('time')" aria-labelledby="access_time"> - <mat-icon [ngClass]="{time: 'unread-icon'}[currentSort]">update</mat-icon> - <span>{{ 'comment-list.sort-list-time' | translate }}</span> + <mat-icon [ngClass]="{time: 'timesort'}[filter.sortType]">update</mat-icon> + <span [ngClass]="{time: 'timesort'}[filter.sortType]">{{ 'comment-list.sort-list-time' | translate }}</span> </button> <button mat-menu-item matTooltip="{{ 'comment-list.vote-asc' | translate }}" - (click)="sortComments(votedesc)" + (click)="applySortingByKey('votedesc')" aria-labelledby="keyboard_arrow_up"> - <mat-icon [ngClass]="{votedesc: 'up'}[currentSort]">thumb_up</mat-icon> + <mat-icon [ngClass]="{votedesc: 'up'}[filter.sortType]">thumb_up</mat-icon> <span>{{ 'comment-list.sort-vote-asc' | translate }}</span> </button> <button mat-menu-item matTooltip="{{ 'comment-list.vote-desc' | translate }}" - (click)="sortComments(voteasc)" + (click)="applySortingByKey('voteasc')" aria-labelledby="keyboard_arrow_down"> - <mat-icon [ngClass]="{voteasc: 'down'}[currentSort]">thumb_down</mat-icon> + <mat-icon [ngClass]="{voteasc: 'down'}[filter.sortType]">thumb_down</mat-icon> <span>{{ 'comment-list.sort-vote-desc' | translate }}</span> </button> </mat-menu> @@ -99,7 +108,7 @@ <mat-menu #timeMenu="matMenu" xPosition="before"> <div *ngFor="let periodItem of periodsList"> <button mat-menu-item (click)="setTimePeriod(periodItem)" class="period" - [ngClass]="{'selected': periodItem === period}" + [ngClass]="{'selected': periodItem === filter.period}" aria-labelledby="{{periodItem}}"> <span>{{ ('comment-list.select-' + periodItem) | translate }}</span> </button> @@ -122,7 +131,10 @@ [userRole]="user.role" [parseVote]="getVote(current)" [moderator]="true" - (clickedUserNumber)="clickedUserNumber($event)"> + [commentsWrittenByUser]="commentsWrittenByUsers.get(current.creatorId).size" + (clickedOnTag)="applyFilterByKey('tag', $event)" + (clickedUserNumber)="applyFilterByKey('userNumber', $event)" + (clickedOnKeyword)="applyFilterByKey('keyword', $event)"> </app-comment> </div> <ars-mat-paginator @@ -136,7 +148,7 @@ <ars-row [height]="64"> </ars-row> -<div *ngIf="comments && (commentsFilteredByTime.length < 1 && period === 'time-all' || comments.length === 0) && !isLoading" +<div *ngIf="comments && (commentsFilteredByTime.length < 1 && filter.period === 'time-all' || comments.length === 0) && !isLoading" fxLayout="row" fxLayoutAlign="center center" class="no-comments"> @@ -145,7 +157,7 @@ </div> <div *ngIf="(filteredComments && filteredComments.length === 0 && hideCommentsList) - || (comments && commentsFilteredByTime.length === 0 && period !== 'time-all') && !isLoading && comments.length > 0" + || (comments && commentsFilteredByTime.length === 0 && filter.period !== 'time-all') && !isLoading && comments.length > 0" fxLayout="row" fxLayoutAlign="center center" class="no-comments"> diff --git a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.scss b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.scss index 34b3f2feb31cf55bd7c878a39d808a1c3ddd54da..b39d91b167a734eccd46af85f5c1475478ee931f 100644 --- a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.scss +++ b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.scss @@ -101,6 +101,10 @@ h3 { margin: 10px; } +.timesort { + color: var(--primary); +} + .up { color: var(--green); } diff --git a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts index 96ad723dc770ba147dde5249889d7451553a3202..7cf488d0a7634df86b6e2bf5bb163b8d73caf217 100644 --- a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts +++ b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts @@ -21,8 +21,16 @@ import { DeleteCommentsComponent } from '../../creator/_dialogs/delete-comments/ import { Export } from '../../../models/export'; import { NotificationService } from '../../../services/util/notification.service'; import { BonusTokenService } from '../../../services/http/bonus-token.service'; -import { CommentFilter, Period } from '../../../utils/filter-options'; +import { Period } from '../../../utils/filter-options'; import { PageEvent } from '@angular/material/paginator'; +import { + CommentListFilter, + FilterType, + FilterTypeKey, + SortType, + SortTypeKey +} from '../../shared/comment-list/comment-list.filter'; +import { ModeratorService } from '../../../services/http/moderator.service'; @Component({ @@ -47,7 +55,6 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { voteasc = 'voteasc'; votedesc = 'votedesc'; time = 'time'; - currentSort: string; read = 'read'; unread = 'unread'; favorite = 'favorite'; @@ -56,21 +63,19 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { ack = 'ack'; bookmark = 'bookmark'; userNumber = 'userNumber'; - currentFilter = ''; commentVoteMap = new Map<string, Vote>(); scroll = false; scrollExtended = false; - searchInput = ''; search = false; searchPlaceholder = ''; periodsList = Object.values(Period); - period: Period = Period.twoWeeks; - fromNow: number; headerInterface = null; pageIndex = 0; pageSize = 10; pageSizeOptions = [5, 10, 25]; showFirstLastButtons = true; + commentsWrittenByUsers: Map<string, Set<string>> = new Map<string, Set<string>>(); + filter: CommentListFilter; constructor( private commentService: CommentService, @@ -83,12 +88,14 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { private router: Router, private notificationService: NotificationService, private translationService: TranslateService, - private bonusTokenService: BonusTokenService + private bonusTokenService: BonusTokenService, + private moderationService: ModeratorService ) { langService.langEmitter.subscribe(lang => translateService.use(lang)); + this.filter = CommentListFilter.loadCurrentFilter(); } - handlePageEvent(e:PageEvent){ + handlePageEvent(e: PageEvent) { this.pageIndex = e.pageIndex; this.pageSize = e.pageSize; } @@ -113,25 +120,25 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { } dialogRef.componentInstance.tags = tags; dialogRef.afterClosed() - .subscribe(result => { - if (!result || result === 'abort') { - return; - } else { - updRoom.tags = result; - this.roomService.updateRoom(updRoom) - .subscribe((room) => { - this.room = room; - this.translateService.get('room-page.changes-successful').subscribe(msg => { - this.notificationService.show(msg); - }); - }, - error => { - this.translateService.get('room-page.changes-gone-wrong').subscribe(msg => { - this.notificationService.show(msg); - }); - }); - } - }); + .subscribe(result => { + if (!result || result === 'abort') { + return; + } else { + updRoom.tags = result; + this.roomService.updateRoom(updRoom) + .subscribe((room) => { + this.room = room; + this.translateService.get('room-page.changes-successful').subscribe(msg => { + this.notificationService.show(msg); + }); + }, + error => { + this.translateService.get('room-page.changes-gone-wrong').subscribe(msg => { + this.notificationService.show(msg); + }); + }); + } + }); }); nav('deleteQuestions', () => { const dialogRef = this.dialog.open(DeleteCommentsComponent, { @@ -139,14 +146,14 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { }); dialogRef.componentInstance.roomId = this.roomId; dialogRef.afterClosed() - .subscribe(result => { - if (result === 'delete') { - this.translationService.get('room-page.comments-deleted').subscribe(msg => { - this.notificationService.show(msg); - }); - this.commentService.deleteCommentsByRoomId(this.roomId).subscribe(); - } - }); + .subscribe(result => { + if (result === 'delete') { + this.translationService.get('room-page.comments-deleted').subscribe(msg => { + this.notificationService.show(msg); + }); + this.commentService.deleteCommentsByRoomId(this.roomId).subscribe(); + } + }); }); nav('exportQuestions', () => { const exp: Export = new Export( @@ -166,6 +173,7 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { } ngOnDestroy() { + this.filter.save(); if (this.headerInterface) { this.headerInterface.unsubscribe(); } @@ -175,8 +183,12 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { this.initNavigation(); this.roomId = localStorage.getItem(`roomId`); const userId = this.user.id; + this.filter.updateUserId(userId); this.userRole = this.user.role; - this.roomService.getRoom(this.roomId).subscribe(room => this.room = room); + this.roomService.getRoom(this.roomId).subscribe(room => { + this.room = room; + this.filter.updateRoom(room); + }); this.hideCommentsList = false; this.wsCommentService.getModeratorCommentStream(this.roomId).subscribe((message: Message) => { this.parseIncomingModeratorMessage(message); @@ -187,30 +199,23 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { this.translateService.use(localStorage.getItem('currentLang')); this.deviceType = localStorage.getItem('deviceType'); this.isSafari = localStorage.getItem('isSafari'); - this.currentSort = this.votedesc; - this.commentService.getRejectedComments(this.roomId) - .subscribe(comments => { - this.comments = comments; - this.setTimePeriod(this.period); - this.isLoading = false; + this.filter.sortType = SortType.votedesc; + this.moderationService.get(this.roomId) + .subscribe((mods) => { + this.filter.updateModerators(mods.map(mod => mod.accountId)); + + this.commentService.getRejectedComments(this.roomId) + .subscribe(comments => { + this.comments = comments; + this.setTimePeriod(this.filter.period); + this.isLoading = false; + }); }); this.translateService.get('comment-list.search').subscribe(msg => { this.searchPlaceholder = msg; }); } - private getCurrentFilter() { - const filter = new CommentFilter(); - filter.filterSelected = this.currentFilter; - filter.periodSet = this.period; - - if (filter.periodSet === Period.fromNow) { - filter.timeStampNow = new Date().getTime(); - } - - CommentFilter.currentFilter = filter; - } - checkScroll(): void { const currentScroll = document.documentElement.scrollTop; this.scroll = currentScroll >= 65; @@ -223,18 +228,14 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { searchComments(): void { this.search = true; - if (this.searchInput && this.searchInput.length > 1) { + if (this.filter.currentSearch) { this.hideCommentsList = true; - this.filteredComments = this.comments - .filter(c => this.checkIfIncludesKeyWord(c.body, this.searchInput) - || (!!c.answer ? this.checkIfIncludesKeyWord(c.answer, this.searchInput) : false)); + this.filteredComments = this.filter.filterCommentsBySearch(this.comments); + } else if (!this.filter.filterType) { + this.hideCommentsList = false; } } - checkIfIncludesKeyWord(body: string, keyword: string) { - return body.toLowerCase().includes(keyword.toLowerCase()); - } - activateSearch() { this.translateService.get('comment-list.search').subscribe(msg => { this.searchPlaceholder = msg; @@ -243,6 +244,13 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { this.searchField.nativeElement.focus(); } + abortSearch() { + this.hideCommentsList = false; + this.filter.currentSearch = ''; + this.search = false; + this.refreshFiltering(); + } + getComments(): void { this.isLoading = false; let commentThreshold = -10; @@ -254,7 +262,7 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { this.comments = this.comments.filter(x => x.score >= commentThreshold); } } - this.setTimePeriod(this.period); + this.setTimePeriod(this.filter.period); } getVote(comment: Comment): Vote { @@ -278,7 +286,7 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { this.comments = this.comments.filter(function (el) { return el.id !== payload.id; }); - this.setTimePeriod(this.period); + this.setTimePeriod(this.filter.period); } switch (key) { case this.read: @@ -318,7 +326,7 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { } break; } - this.setTimePeriod(this.period); + this.setTimePeriod(this.filter.period); if (this.hideCommentsList) { this.searchComments(); } @@ -336,70 +344,49 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { c.timestamp = payload.timestamp; c.creatorId = payload.creatorId; c.keywordsFromQuestioner = payload.keywordsFromQuestioner ? - JSON.parse(payload.keywordsFromQuestioner as unknown as string) : null; + JSON.parse(payload.keywordsFromQuestioner as unknown as string) : null; c.userNumber = this.commentService.hashCode(c.creatorId); this.comments = this.comments.concat(c); break; } - this.setTimePeriod(this.period); + this.setTimePeriod(this.filter.period); if (this.hideCommentsList) { this.searchComments(); } } - filterComments(type: string, compare?: any): void { - this.currentFilter = type; - if (type === '') { - this.filteredComments = this.commentsFilteredByTime; + refreshFiltering(): void { + this.commentsWrittenByUsers.clear(); + for (const comment of this.comments) { + let set = this.commentsWrittenByUsers.get(comment.creatorId); + if (!set) { + set = new Set<string>(); + this.commentsWrittenByUsers.set(comment.creatorId, set); + } + set.add(comment.id); + } + this.isLoading = false; + if (this.search) { + this.filteredComments = this.filter.filterCommentsBySearch(this.comments); return; } - this.filteredComments = this.commentsFilteredByTime.filter(c => { - switch (type) { - case this.correct: - return c.correct === CorrectWrong.CORRECT ? 1 : 0; - case this.wrong: - return c.correct === CorrectWrong.WRONG ? 1 : 0; - case this.favorite: - return c.favorite; - case this.bookmark: - return c.bookmark; - case this.read: - return c.read; - case this.unread: - return !c.read; - case this.userNumber: - return c.userNumber === compare; - } - }); - this.hideCommentsList = true; - this.sortComments(this.currentSort); - } - - clickedUserNumber(usrNumber: number): void { - this.filterComments(this.userNumber, usrNumber); + this.commentsFilteredByTime = this.filter.filterCommentsByTime(this.comments); + this.hideCommentsList = !!this.filter.filterType; + this.filteredComments = this.hideCommentsList ? + this.filter.filterCommentsByType(this.commentsFilteredByTime) : this.commentsFilteredByTime; + this.filter.sortCommentsBySortType(this.filteredComments); } - sort(array: any[], type: string): void { - array.sort((a, b) => { - if (type === this.voteasc) { - return (a.score > b.score) ? 1 : (b.score > a.score) ? -1 : 0; - } else if (type === this.votedesc) { - return (b.score > a.score) ? 1 : (a.score > b.score) ? -1 : 0; - } - const dateA = new Date(a.timestamp), dateB = new Date(b.timestamp); - if (type === this.time) { - return (+dateB > +dateA) ? 1 : (+dateA > +dateB) ? -1 : 0; - } - }); + applyFilterByKey(type: FilterTypeKey, compare?: any): void { + this.pageIndex = 0; + this.filter.filterType = FilterType[type]; + this.filter.filterCompare = compare; + this.refreshFiltering(); } - sortComments(type: string): void { - if (this.hideCommentsList === true) { - this.sort(this.filteredComments, type); - } else { - this.sort(this.commentsFilteredByTime, type); - } - this.currentSort = type; + applySortingByKey(type: SortTypeKey) { + this.filter.sortType = SortType[type]; + this.refreshFiltering(); } switchToCommentList(): void { @@ -411,44 +398,12 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { } this.router.navigate([`/${role}/room/${this.room.shortId}/comments`]); } + setTimePeriod(period?: Period) { if (period) { - this.period = period; - this.fromNow = null; + this.filter.period = period; + this.filter.fromNow = null; } - const currentTime = new Date(); - const hourInSeconds = 3600000; - let periodInSeconds; - if (this.period !== Period.all) { - switch (this.period) { - case Period.fromNow: - if (!this.fromNow) { - this.fromNow = new Date().getTime(); - } - break; - case Period.oneHour: - periodInSeconds = hourInSeconds; - break; - case Period.threeHours: - periodInSeconds = hourInSeconds * 2; - break; - case Period.oneDay: - periodInSeconds = hourInSeconds * 24; - break; - case Period.oneWeek: - periodInSeconds = hourInSeconds * 168; - break; - case Period.twoWeeks: - periodInSeconds = hourInSeconds * 336; - break; - } - this.commentsFilteredByTime = this.comments - .filter(c => new Date(c.timestamp).getTime() >= - (this.period === Period.fromNow ? this.fromNow : (currentTime.getTime() - periodInSeconds))); - } else { - this.commentsFilteredByTime = this.comments; - } - - this.filterComments(this.currentFilter); + this.refreshFiltering(); } } diff --git a/src/app/components/shared/comment-list/comment-list.component.html b/src/app/components/shared/comment-list/comment-list.component.html index e9ad89156959299c38bc985b8539cbe57f27ff33..fbff84b4b2518059e2fc911ddab877635aee8081 100644 --- a/src/app/components/shared/comment-list/comment-list.component.html +++ b/src/app/components/shared/comment-list/comment-list.component.html @@ -7,8 +7,8 @@ <button id="filter-close-button" mat-icon-button class="searchBarButton" - *ngIf="currentFilter !== ''" - (click)="filterComments('');" + *ngIf="filter.filterType" + (click)="applyFilterByKey(null)" aria-labelledby="close_filter"> <mat-icon class="searchBarIcon red">close</mat-icon> </button> @@ -22,14 +22,14 @@ [ngClass]="{'desktop-input': deviceType === 'desktop', 'mobile-input': deviceType === 'mobile' && !search, 'mobile-input-2': deviceType === 'mobile' && search }" (input)="searchComments()" - [(ngModel)]="searchInput" + [(ngModel)]="filter.currentSearch" [placeholder]="searchPlaceholder" aria-labelledby="search-box-input-description"> <button id="search_close-button" mat-icon-button class="searchBarButton close red" - *ngIf="searchInput !== '' || search" - (click)="hideCommentsList=false; searchInput = ''; search = false;" + *ngIf="filter.currentSearch || search" + (click)="abortSearch()" aria-labelledby="close_search"> <mat-icon>close</mat-icon> </button> @@ -49,7 +49,7 @@ <button mat-icon-button class="searchBarButton" - (click)="activateSearch(); filterComments('')" + (click)="activateSearch(); applyFilterByKey(null)" *ngIf="deviceType === 'mobile' && !search && comments && comments.length > 0"> <mat-icon class="searchBarIcon">search</mat-icon> </button> @@ -81,7 +81,7 @@ aria-labelledby="time_settings" *ngIf="!searchBox.value && comments && comments.length > 0 && !search" [matMenuTriggerFor]="timeMenu" - [ngClass]="{'active-filter': period !== 'time-all'}" + [ngClass]="{'active-filter': filter.period !== 'time-all'}" matTooltip="{{ 'comment-list.select-time' | translate }}"> <mat-icon class="searchBarIcon">history</mat-icon> </button> @@ -91,7 +91,7 @@ aria-labelledby="pause" class="freezeButton" *ngIf="!searchBox.value && !search && !freeze && comments.length > 2" - (click)="pauseCommentStream()" + (click)="activateCommentStream(true)" matTooltip="{{ 'comment-list.pause-comments' | translate }}"> <mat-icon class="freezeIcon">pause</mat-icon> </button> @@ -101,7 +101,7 @@ aria-labelledby="play" class="freezeButton" *ngIf="!searchBox.value && !search && freeze" - (click)="playCommentStream()" + (click)="activateCommentStream(false)" matTooltip="{{ 'comment-list.play-comments' | translate }}"> <mat-icon class="playIcon">play_arrow</mat-icon> </button> @@ -114,7 +114,7 @@ <button mat-menu-item (click)="setTimePeriod(periodItem)" class="period" - [ngClass]="{'selected': periodItem === period}" + [ngClass]="{'selected': periodItem === filter.period}" aria-labelledby="{{periodItem}}"> <span>{{ ('comment-list.select-' + periodItem) | translate }}</span> </button> @@ -125,24 +125,24 @@ xPosition="before"> <button mat-menu-item - (click)="sortComments(time)" + (click)="applySortingByKey('time')" aria-labelledby="access_time"> - <mat-icon [ngClass]="{time: 'timesort'}[currentSort]">update</mat-icon> - <span [ngClass]="{time: 'timesort'}[currentSort]">{{ 'comment-list.sort-list-time' | translate }}</span> + <mat-icon [ngClass]="{time: 'timesort'}[filter.sortType]">update</mat-icon> + <span [ngClass]="{time: 'timesort'}[filter.sortType]">{{ 'comment-list.sort-list-time' | translate }}</span> </button> <button mat-menu-item - (click)="sortComments(votedesc)" + (click)="applySortingByKey('votedesc')" aria-labelledby="keyboard_arrow_up"> - <mat-icon [ngClass]="{votedesc: 'up'}[currentSort]">thumb_up</mat-icon> - <span [ngClass]="{votedesc: 'up'}[currentSort]">{{ 'comment-list.sort-vote-asc' | translate }}</span> + <mat-icon [ngClass]="{votedesc: 'up'}[filter.sortType]">thumb_up</mat-icon> + <span [ngClass]="{votedesc: 'up'}[filter.sortType]">{{ 'comment-list.sort-vote-asc' | translate }}</span> </button> <button mat-menu-item - (click)="sortComments(voteasc)" + (click)="applySortingByKey('voteasc')" aria-labelledby="keyboard_arrow_down"> - <mat-icon [ngClass]="{voteasc: 'down'}[currentSort]">thumb_down</mat-icon> - <span [ngClass]="{voteasc: 'down'}[currentSort]">{{ 'comment-list.sort-vote-desc' | translate }}</span> + <mat-icon [ngClass]="{voteasc: 'down'}[filter.sortType]">thumb_down</mat-icon> + <span [ngClass]="{voteasc: 'down'}[filter.sortType]">{{ 'comment-list.sort-vote-desc' | translate }}</span> </button> </mat-menu> @@ -152,53 +152,53 @@ <div> <button mat-menu-item - (click)="filterComments(favorite)" + (click)="applyFilterByKey('favorite')" aria-labelledby="grade"> <mat-icon class="star" - [ngClass]="{favorite: 'favorite-icon'}[currentFilter]">grade + [ngClass]="{favorite: 'favorite-icon'}[filter.filterType]">grade </mat-icon> <span - [ngClass]="{favorite: 'favorite-icon'}[currentFilter]">{{ 'comment-list.filter-favorite' | translate }}</span> + [ngClass]="{favorite: 'favorite-icon'}[filter.filterType]">{{ 'comment-list.filter-favorite' | translate }}</span> </button> <button mat-menu-item - (click)="filterComments(bookmark)" + (click)="applyFilterByKey('bookmark')" aria-labelledby="bookmark"> <mat-icon class="bookmark" - [ngClass]="{bookmark: 'bookmark-icon'}[currentFilter]">bookmark + [ngClass]="{bookmark: 'bookmark-icon'}[filter.filterType]">bookmark </mat-icon> <span - [ngClass]="{bookmark: 'bookmark-icon'}[currentFilter]">{{ 'comment-list.filter-bookmark' | translate }}</span> + [ngClass]="{bookmark: 'bookmark-icon'}[filter.filterType]">{{ 'comment-list.filter-bookmark' | translate }}</span> </button> <button mat-menu-item (focus)="hideCommentsList=true" - (click)="filterComments(answer)" + (click)="applyFilterByKey('answer')" aria-labelledby="comment"> <mat-icon class="answer" - [ngClass]="{answer: 'answered-icon'}[currentFilter]">comment + [ngClass]="{answer: 'answered-icon'}[filter.filterType]">comment </mat-icon> <span - [ngClass]="{answer: 'answered-icon'}[currentFilter]">{{ 'comment-list.filter-answered' | translate }}</span> + [ngClass]="{answer: 'answered-icon'}[filter.filterType]">{{ 'comment-list.filter-answered' | translate }}</span> </button> <button mat-menu-item (focus)="hideCommentsList=true" - (click)="filterComments(unanswered)" + (click)="applyFilterByKey('unanswered')" aria-labelledby="comment"> <mat-icon class="unanswered" - [ngClass]="{unanswered: 'unanswered-icon'}[currentFilter]">comment + [ngClass]="{unanswered: 'unanswered-icon'}[filter.filterType]">comment </mat-icon> <span - [ngClass]="{unanswered: 'unanswered-icon'}[currentFilter]">{{ 'comment-list.filter-unanswered' | translate }}</span> + [ngClass]="{unanswered: 'unanswered-icon'}[filter.filterType]">{{ 'comment-list.filter-unanswered' | translate }}</span> </button> <button mat-menu-item (focus)="hideCommentsList=true" - (click)="filterComments(owner)" + (click)="applyFilterByKey('owner')" aria-labelledby="comment"> - <mat-icon [ngClass]="{owner: 'owner-icon'}[currentFilter]">person_pin_circle</mat-icon> - <span [ngClass]="{owner: 'owner-icon'}[currentFilter]">{{ 'comment-list.filter-owner' | translate }}</span> + <mat-icon [ngClass]="{owner: 'owner-icon'}[filter.filterType]">person_pin_circle</mat-icon> + <span [ngClass]="{owner: 'owner-icon'}[filter.filterType]">{{ 'comment-list.filter-owner' | translate }}</span> </button> <!-- @@ -227,7 +227,7 @@ <button mat-menu-item (focus)="hideCommentsList=false" - (click)="sortComments(currentSort); filterComments('')" + (click)="applyFilterByKey(null)" aria-labelledby="close"> <mat-icon>close</mat-icon> <span>{{ 'comment-list.filter-reset' | translate }}</span> @@ -275,9 +275,9 @@ [user]="user" [disabled]="!commentsEnabled" [commentsWrittenByUser]="commentsWrittenByUsers.get(current.creatorId).size" - (clickedOnTag)="clickedOnTag($event)" - (clickedUserNumber)="clickedUserNumber($event)" - (clickedOnKeyword)="clickedOnKeyword($event)" + (clickedOnTag)="applyFilterByKey('tag', $event)" + (clickedUserNumber)="applyFilterByKey('userNumber', $event)" + (clickedOnKeyword)="applyFilterByKey('keyword', $event)" (votedComment)="votedComment($event)"> </app-comment> <ars-mat-paginator @@ -297,7 +297,7 @@ <!-- No Questions Present --> <div - *ngIf="comments && (commentsFilteredByTime.length < 1 && period === 'time-all' || comments.length === 0) && !isLoading" + *ngIf="comments && (commentsFilteredByTime.length < 1 && filter.period === 'time-all' || comments.length === 0) && !isLoading" fxLayout="row" fxLayoutAlign="center center" class="no-comments"> @@ -305,7 +305,7 @@ </div> <div *ngIf="(filteredComments && filteredComments.length === 0 && hideCommentsList) - || (comments && commentsFilteredByTime.length === 0 && period !== 'time-all') && !isLoading && comments.length > 0" + || (comments && commentsFilteredByTime.length === 0 && filter.period !== 'time-all') && !isLoading && comments.length > 0" fxLayout="row" fxLayoutAlign="center center" class="no-comments"> diff --git a/src/app/components/shared/comment-list/comment-list.component.ts b/src/app/components/shared/comment-list/comment-list.component.ts index 1e73a75e1abb5c8ba331c4848ea5ded383b1c393..ca87614756f787d498bf547f5f8a0f50e3cf0d20 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -11,7 +11,6 @@ import { Room } from '../../../models/room'; import { RoomService } from '../../../services/http/room.service'; import { VoteService } from '../../../services/http/vote.service'; import { NotificationService } from '../../../services/util/notification.service'; -import { CorrectWrong } from '../../../models/correct-wrong.enum'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { EventService } from '../../../services/util/event.service'; import { Subscription } from 'rxjs'; @@ -34,6 +33,7 @@ import { ActiveUserService } from '../../../services/http/active-user.service'; import { OnboardingService } from '../../../services/util/onboarding.service'; import { WorkerDialogComponent } from '../_dialogs/worker-dialog/worker-dialog.component'; import { PageEvent } from '@angular/material/paginator'; +import { CommentListFilter, FilterType, FilterTypeKey, SortType, SortTypeKey } from './comment-list.filter'; export interface CommentListData { currentFilter: CommentFilter; @@ -60,60 +60,32 @@ export class CommentListComponent implements OnInit, OnDestroy { deviceType: string; isSafari: string; isLoading = true; - voteasc = 'voteasc'; - votedesc = 'votedesc'; - time = 'time'; - currentSort: string; - read = 'read'; - unread = 'unread'; - favorite = 'favorite'; - correct = 'correct'; - wrong = 'wrong'; - ack = 'ack'; - bookmark = 'bookmark'; - moderator = 'moderator'; - lecturer = 'lecturer'; - tag = 'tag'; - selectedTag = ''; - userNumber = 'userNumber'; - keyword = 'keyword'; - selectedKeyword = ''; - answer = 'answer'; - unanswered = 'unanswered'; - owner = 'owner'; - currentFilter = ''; - currentFilterCompare: any = null; commentVoteMap = new Map<string, Vote>(); scroll = false; scrollExtended = false; - searchInput = ''; search = false; searchPlaceholder = ''; moderationEnabled = true; directSend = true; - thresholdEnabled = false; newestComment: string; freeze = false; commentStream: Subscription; periodsList = Object.values(Period); headerInterface = null; - period: Period = Period.twoWeeks; - fromNow: number; - moderatorIds: string[]; commentsEnabled: boolean; - userNumberSelection = 0; createCommentWrapper: CreateCommentWrapper = null; isJoyrideActive = false; focusCommentId = ''; activeUsers = 0; - commentsWrittenByUsers: Map<string, Set<string>> = new Map<string, Set<string>>(); - private _subscriptionEventServiceTagConfig = null; - private _subscriptionEventServiceRoomData = null; - private _subscriptionRoomService = null; pageIndex = 0; pageSize = 10; pageSizeOptions = [5, 10, 25]; showFirstLastButtons = true; + commentsWrittenByUsers: Map<string, Set<string>> = new Map<string, Set<string>>(); + filter: CommentListFilter; + private _subscriptionEventServiceTagConfig = null; + private _subscriptionEventServiceRoomData = null; + private _subscriptionRoomService = null; constructor( private commentService: CommentService, @@ -144,9 +116,10 @@ export class CommentListComponent implements OnInit, OnDestroy { this.searchPlaceholder = msg; }); }); + this.filter = CommentListFilter.loadCurrentFilter(); } - handlePageEvent(e:PageEvent){ + handlePageEvent(e: PageEvent) { this.pageIndex = e.pageIndex; this.pageSize = e.pageSize; } @@ -154,7 +127,7 @@ export class CommentListComponent implements OnInit, OnDestroy { initNavigation() { this._subscriptionEventServiceTagConfig = this.eventService.on<string>('setTagConfig').subscribe(tag => { this.setTimePeriod(Period.all); - this.clickedOnKeyword(tag); + this.applyFilterByKey('keyword', tag); }); this._subscriptionEventServiceRoomData = this.eventService.on<string>('pushCurrentRoomData').subscribe(_ => { this.eventService.broadcast('currentRoomData', { @@ -239,6 +212,7 @@ export class CommentListComponent implements OnInit, OnDestroy { this.authenticationService.watchUser.subscribe(newUser => { if (newUser) { this.user = newUser; + this.filter.updateUserId(this.user.id); if (this.userRole === UserRole.PARTICIPANT) { this.voteService.getByRoomIdAndUserID(this.roomId, this.user.id).subscribe(votes => { for (const v of votes) { @@ -254,21 +228,13 @@ export class CommentListComponent implements OnInit, OnDestroy { this.authenticationService.checkAccess(this.shortId); this.authenticationService.guestLogin(UserRole.PARTICIPANT).subscribe(r => { this.roomService.getRoomByShortId(this.shortId).subscribe(room => { - this.room = room; - this.roomId = room.id; + this.receiveRoom(room); this._subscriptionRoomService = this.wsRoomService.getRoomStream(this.roomId).subscribe(msg => { const message = JSON.parse(msg.body); if (message.type === 'RoomPatched') { - this.room = message.payload.changes; - this.roomId = this.room.id; - this.moderationEnabled = this.room.moderated; - this.directSend = this.room.directSend; - this.commentsEnabled = (this.userRole > UserRole.PARTICIPANT) || !this.room.questionsBlocked; + this.receiveRoom(message.payload.changes); } }); - this.moderationEnabled = this.room.moderated; - this.directSend = this.room.directSend; - this.commentsEnabled = (this.userRole > UserRole.PARTICIPANT) || !this.room.questionsBlocked; this.createCommentWrapper = new CreateCommentWrapper(this.translateService, this.notificationService, this.commentService, this.dialog, this.room); localStorage.setItem('moderationEnabled', JSON.stringify(this.moderationEnabled)); @@ -277,8 +243,7 @@ export class CommentListComponent implements OnInit, OnDestroy { this.authenticationService.setAccess(this.shortId, UserRole.PARTICIPANT); } this.moderatorService.get(this.roomId).subscribe(list => { - this.moderatorIds = list.map(m => m.accountId); - this.moderatorIds.push(this.room.ownerId); + this.filter.updateModerators(list.map(m => m.accountId)); this.roomDataService.getRoomData(this.room.id).subscribe(comments => { if (comments === null) { @@ -286,7 +251,11 @@ export class CommentListComponent implements OnInit, OnDestroy { } this.comments = comments; this.generateKeywordsIfEmpty(); - this.getComments(); + if (this.filter.currentSearch) { + this.search = true; + this.hideCommentsList = true; + } + this.refreshFiltering(); this.eventService.broadcast('commentListCreated', null); this.isJoyrideActive = this.onboardingService.startDefaultTour(); }); @@ -295,7 +264,6 @@ export class CommentListComponent implements OnInit, OnDestroy { }); }); }); - this.currentSort = this.votedesc; this.hideCommentsList = false; this.translateService.use(localStorage.getItem('currentLang')); this.deviceType = localStorage.getItem('deviceType'); @@ -306,6 +274,7 @@ export class CommentListComponent implements OnInit, OnDestroy { } ngOnDestroy() { + this.filter.save(); if (!this.freeze && this.commentStream) { this.commentStream.unsubscribe(); } @@ -336,31 +305,47 @@ export class CommentListComponent implements OnInit, OnDestroy { searchComments(): void { this.search = true; - if (this.searchInput) { - if (this.searchInput.length > 1) { - this.hideCommentsList = true; - this.filteredComments = this.comments - .filter(c => this.checkIfIncludesKeyWord(c.body, this.searchInput) - || (!!c.answer ? this.checkIfIncludesKeyWord(c.answer, this.searchInput) : false)); - } - } else if (this.searchInput.length === 0 && this.currentFilter === '') { + if (this.filter.currentSearch) { + this.hideCommentsList = true; + this.filteredComments = this.filter.filterCommentsBySearch(this.comments); + } else if (!this.filter.filterType) { this.hideCommentsList = false; } } - checkIfIncludesKeyWord(body: string, keyword: string) { - return body.toLowerCase().includes(keyword.toLowerCase()); - } - activateSearch() { this.search = true; this.searchField.nativeElement.focus(); } - getComments(): void { - this.thresholdEnabled = !!this.room.threshold; + abortSearch() { + this.hideCommentsList = false; + this.filter.currentSearch = ''; + this.search = false; + this.refreshFiltering(); + } + + refreshFiltering(): void { + this.commentsWrittenByUsers.clear(); + for (const comment of this.comments) { + let set = this.commentsWrittenByUsers.get(comment.creatorId); + if (!set) { + set = new Set<string>(); + this.commentsWrittenByUsers.set(comment.creatorId, set); + } + set.add(comment.id); + } this.isLoading = false; - this.setTimePeriod(); + this.commentsFilteredByTime = this.filter.filterCommentsByTime(this.comments); + this.titleService.attachTitle('(' + this.commentsFilteredByTime.length + ')'); + if (this.search) { + this.filteredComments = this.filter.filterCommentsBySearch(this.comments); + return; + } + this.hideCommentsList = !!this.filter.filterType; + this.filteredComments = this.hideCommentsList ? + this.filter.filterCommentsByType(this.commentsFilteredByTime) : this.commentsFilteredByTime; + this.filter.sortCommentsBySortType(this.filteredComments); } getVote(comment: Comment): Vote { @@ -373,115 +358,16 @@ export class CommentListComponent implements OnInit, OnDestroy { this.dialog.closeAll(); } - filterComments(type: string, compare?: any): void { - this.pageIndex=0; - this.currentFilter = type; - this.currentFilterCompare = compare; - if (type === '') { - this.filteredComments = this.commentsFilteredByTime; - this.hideCommentsList = false; - this.currentFilter = ''; - this.selectedTag = ''; - this.selectedKeyword = ''; - this.userNumberSelection = 0; - this.sortComments(this.currentSort); - return; - } - this.filteredComments = this.commentsFilteredByTime.filter(c => { - switch (type) { - case this.correct: - return c.correct === CorrectWrong.CORRECT ? 1 : 0; - case this.wrong: - return c.correct === CorrectWrong.WRONG ? 1 : 0; - case this.favorite: - return c.favorite; - case this.bookmark: - return c.bookmark; - case this.read: - return c.read; - case this.unread: - return !c.read; - case this.tag: - this.selectedTag = compare; - return c.tag === compare; - case this.userNumber: - return c.userNumber === compare; - case this.keyword: - this.selectedKeyword = compare; - const isInQuestioner = c.keywordsFromQuestioner ? c.keywordsFromQuestioner.findIndex(k => k.text === compare) >= 0 : false; - const isInSpacy = c.keywordsFromSpacy ? c.keywordsFromSpacy.findIndex(k => k.text === compare) >= 0 : false; - return isInQuestioner || isInSpacy; - case this.answer: - return c.answer; - case this.unanswered: - return !c.answer; - case this.owner: - return c.creatorId === this.user.id; - case this.moderator: - return this.moderatorIds.includes(c.creatorId); - case this.lecturer: - return c.createdFromLecturer; - } - }); - const testForModerator = () => { - this.comments.forEach(e => { - this.commentService.role(e).subscribe(i => { - console.log(e, i); - }); - }); - }; - if (type === 'moderator') { - console.log( - 'TEST moderator', - this.moderatorIds, - this.user, - this.room - ); - testForModerator(); - } - this.hideCommentsList = true; - this.sortComments(this.currentSort); - } - - sort(array: any[], type: string): any[] { - const sortedArray = array.sort((a, b) => { - if (type === this.voteasc) { - return (a.score > b.score) ? 1 : (b.score > a.score) ? -1 : 0; - } else if (type === this.votedesc) { - return (b.score > a.score) ? 1 : (a.score > b.score) ? -1 : 0; - } else if (type === this.time) { - const dateA = new Date(a.timestamp); - const dateB = new Date(b.timestamp); - return (+dateB > +dateA) ? 1 : (+dateA > +dateB) ? -1 : 0; - } - }); - return sortedArray.sort((a, b) => this.isCreatedByModeratorOrCreator(a) ? -1 : this.isCreatedByModeratorOrCreator(b) ? 1 : 0); + applyFilterByKey(type: FilterTypeKey, compare?: any): void { + this.pageIndex = 0; + this.filter.filterType = FilterType[type]; + this.filter.filterCompare = compare; + this.refreshFiltering(); } - isCreatedByModeratorOrCreator(comment: Comment): boolean { - return this.moderatorIds.indexOf(comment.creatorId) > -1; - } - - sortComments(type: string): void { - if (this.hideCommentsList === true) { - this.filteredComments = this.sort(this.filteredComments, type); - } else { - this.setComments(this.sort(this.commentsFilteredByTime, type)); - } - this.currentSort = type; - } - - clickedOnTag(tag: string): void { - this.filterComments(this.tag, tag); - } - - clickedOnKeyword(keyword: string): void { - this.filterComments(this.keyword, keyword); - } - - clickedUserNumber(usrNumber: number): void { - this.userNumberSelection = usrNumber; - this.filterComments(this.userNumber, usrNumber); + applySortingByKey(type: SortTypeKey) { + this.filter.sortType = SortType[type]; + this.refreshFiltering(); } votedComment(voteInfo: string) { @@ -489,34 +375,24 @@ export class CommentListComponent implements OnInit, OnDestroy { setTimeout(() => this.focusCommentId = voteInfo, 100); } - pauseCommentStream() { - this.freeze = true; - this.roomDataService.getRoomData(this.roomId, true).subscribe(comments => { - if (comments === null) { - return; - } - this.comments = comments; - this.setComments(comments); - this.getComments(); - }); - this.commentStream.unsubscribe(); - this.translateService.get('comment-list.comment-stream-stopped').subscribe(msg => { - this.notificationService.show(msg); - }); - } - - playCommentStream() { - this.freeze = false; - this.roomDataService.getRoomData(this.roomId).subscribe(comments => { + activateCommentStream(freezed: boolean) { + this.freeze = freezed; + this.roomDataService.getRoomData(this.roomId, freezed).subscribe(comments => { if (comments === null) { return; } this.comments = comments; - this.setComments(comments); - this.getComments(); + this.refreshFiltering(); }); - this.subscribeCommentStream(); - this.translateService.get('comment-list.comment-stream-started').subscribe(msg => { + let message: string; + if (freezed) { + this.commentStream?.unsubscribe(); + message = 'comment-list.comment-stream-stopped'; + } else { + this.subscribeCommentStream(); + message = 'comment-list.comment-stream-started'; + } + this.translateService.get(message).subscribe(msg => { this.notificationService.show(msg); }); } @@ -524,17 +400,13 @@ export class CommentListComponent implements OnInit, OnDestroy { subscribeCommentStream() { this.commentStream = this.roomDataService.receiveUpdates([ { type: 'CommentCreated', finished: true }, - { type: 'CommentPatched', subtype: this.favorite }, - { type: 'CommentPatched', subtype: 'score' }, + { type: 'CommentPatched', subtype: 'favorite' }, { finished: true } ]).subscribe(update => { if (update.type === 'CommentCreated') { this.announceNewComment(update.comment.body); - this.setComments(this.comments); } else if (update.type === 'CommentPatched') { - if (update.subtype === 'score') { - this.getComments(); - } else if (update.subtype === this.favorite) { + if (update.subtype === 'favorite') { if (this.user.id === update.comment.creatorId && update.comment.favorite) { this.translateService.get('comment-list.comment-got-favorited').subscribe(ret => { this.notificationService.show(ret); @@ -543,10 +415,7 @@ export class CommentListComponent implements OnInit, OnDestroy { } } if (update.finished) { - this.setTimePeriod(); - if (this.hideCommentsList) { - this.searchComments(); - } + this.refreshFiltering(); } }); } @@ -555,20 +424,6 @@ export class CommentListComponent implements OnInit, OnDestroy { this.router.navigate([`/moderator/room/${this.room.shortId}/moderator/comments`]); } - setComments(comments: Comment[]) { - this.commentsWrittenByUsers.clear(); - for (const comment of this.comments) { - let set = this.commentsWrittenByUsers.get(comment.creatorId); - if (!set) { - set = new Set<string>(); - this.commentsWrittenByUsers.set(comment.creatorId, set); - } - set.add(comment.id); - } - this.commentsFilteredByTime = comments; - this.titleService.attachTitle('(' + this.commentsFilteredByTime.length + ')'); - } - writeComment() { this.createCommentWrapper.openCreateDialog(this.user) .subscribe(comment => this.focusCommentId = comment && comment.id); @@ -594,57 +449,31 @@ export class CommentListComponent implements OnInit, OnDestroy { }, 450); } - public setTimePeriod(period?: Period) { + setTimePeriod(period?: Period) { if (period) { - this.period = period; - this.fromNow = null; - } - const comments = this.thresholdEnabled ? this.comments.filter(x => x.score >= this.room.threshold) : this.comments; - const currentTime = new Date(); - const hourInSeconds = 3600000; - let periodInSeconds; - if (this.period !== Period.all) { - switch (this.period) { - case Period.fromNow: - if (!this.fromNow) { - this.fromNow = new Date().getTime(); - } - break; - case Period.oneHour: - periodInSeconds = hourInSeconds; - break; - case Period.threeHours: - periodInSeconds = hourInSeconds * 2; - break; - case Period.oneDay: - periodInSeconds = hourInSeconds * 24; - break; - case Period.oneWeek: - periodInSeconds = hourInSeconds * 168; - break; - case Period.twoWeeks: - periodInSeconds = hourInSeconds * 336; - break; - } - this.commentsFilteredByTime = comments - .filter(c => new Date(c.timestamp).getTime() >= - (this.period === Period.fromNow ? this.fromNow : (currentTime.getTime() - periodInSeconds))); - } else { - this.commentsFilteredByTime = comments; + this.filter.period = period; + this.filter.fromNow = null; } + this.refreshFiltering(); + } - this.filterComments(this.currentFilter, this.currentFilterCompare); - this.titleService.attachTitle('(' + this.commentsFilteredByTime.length + ')'); + private receiveRoom(room: Room) { + this.room = room; + this.filter.updateRoom(room); + this.roomId = room.id; + this.moderationEnabled = room.moderated; + this.directSend = room.directSend; + this.commentsEnabled = (this.userRole > UserRole.PARTICIPANT) || !room.questionsBlocked; } private getCurrentFilter(): CommentFilter { const filter = new CommentFilter(); - filter.filterSelected = this.currentFilter; + filter.filterSelected = this.filter.filterType; filter.paused = this.freeze; - filter.periodSet = this.period; - filter.keywordSelected = this.selectedKeyword; - filter.tagSelected = this.selectedTag; - filter.userNumberSelected = this.userNumberSelection; + filter.periodSet = this.filter.period; + filter.keywordSelected = this.filter.filterCompare; + filter.tagSelected = this.filter.filterCompare; + filter.userNumberSelected = this.filter.filterCompare; if (filter.periodSet === Period.fromNow) { filter.timeStampNow = new Date().getTime(); diff --git a/src/app/components/shared/comment-list/comment-list.filter.ts b/src/app/components/shared/comment-list/comment-list.filter.ts new file mode 100644 index 0000000000000000000000000000000000000000..016fe74c8a43ccab2dddf222c955b3d0930ed64b --- /dev/null +++ b/src/app/components/shared/comment-list/comment-list.filter.ts @@ -0,0 +1,248 @@ +import { Period } from '../../../utils/filter-options'; +import { Comment } from '../../../models/comment'; +import { CorrectWrong } from '../../../models/correct-wrong.enum'; +import { Room } from '../../../models/room'; + +export enum FilterType { + voteasc = 'voteasc', + votedesc = 'votedesc', + time = 'time', + read = 'read', + unread = 'unread', + favorite = 'favorite', + correct = 'correct', + wrong = 'wrong', + ack = 'ack', + bookmark = 'bookmark', + moderator = 'moderator', + lecturer = 'lecturer', + tag = 'tag', + userNumber = 'userNumber', + keyword = 'keyword', + answer = 'answer', + unanswered = 'unanswered', + owner = 'owner', +} + +export type FilterTypeKey = keyof typeof FilterType; + +export enum SortType { + voteasc = 'voteasc', + votedesc = 'votedesc', + time = 'time' +} + +export type SortTypeKey = keyof typeof SortType; + +const DEFAULT_PERIOD = Period.all; +const DEFAULT_SORT = SortType.time; + +export class CommentListFilter { + //own properties + period: Period; + fromNow: number; + filterType: FilterType; + filterCompare: any; + sortType: SortType; + currentSearch: string; + //dependencies to other values + private userId: string; + private moderatorIds: Set<string>; + private threshold: number; + private ownerId: string; + private lastRoomId: string; + + constructor(obj) { + if (!obj) { + this.resetToDefault(); + return; + } + this.period = obj.period; + this.fromNow = obj.fromNow; + this.filterType = obj.filterType; + this.filterCompare = obj.filterCompare; + this.sortType = obj.sortType; + this.currentSearch = obj.currentSearch; + this.lastRoomId = obj.lastRoomId; + } + + static loadCurrentFilter(): CommentListFilter { + return new CommentListFilter(JSON.parse(localStorage.getItem('currentFilter'))); + } + + resetToDefault() { + this.period = DEFAULT_PERIOD; + this.fromNow = null; + this.filterType = null; + this.filterCompare = null; + this.sortType = DEFAULT_SORT; + this.currentSearch = ''; + } + + updateRoom(room: Room) { + if (room.id !== this.lastRoomId) { + this.resetToDefault(); + } + this.ownerId = room.ownerId; + this.lastRoomId = room.id; + this.threshold = room.threshold; + } + + updateUserId(userId: string) { + this.userId = userId; + } + + updateModerators(moderators: string[]) { + this.moderatorIds = new Set<string>([...moderators]); + } + + save() { + const ownerId = this.ownerId; + const threshold = this.threshold; + const userId = this.userId; + const moderatorIds = this.moderatorIds; + this.ownerId = this.threshold = this.userId = this.moderatorIds = undefined; + localStorage.setItem('currentFilter', JSON.stringify(this)); + this.ownerId = ownerId; + this.threshold = threshold; + this.userId = userId; + this.moderatorIds = moderatorIds; + } + + get thresholdEnabled() { + return !!this.threshold; + } + + filterCommentsBySearch(comments: Comment[]): Comment[] { + const search = this.currentSearch.toLowerCase(); + return comments + .filter(c => + c.body.toLowerCase().includes(search) || + c.answer?.toLowerCase().includes(search) || + c.keywordsFromSpacy?.some(e => e.text.toLowerCase().includes(search)) || + c.keywordsFromQuestioner?.some(e => e.text.toLowerCase().includes(search)) || + c.questionerName?.toLowerCase().includes(search) + ); + } + + filterCommentsByTime(comments: Comment[], moderation = false): Comment[] { + const thresholdComments = + this.thresholdEnabled && !moderation ? comments.filter(comment => comment.score >= this.threshold) : comments; + if (this.period === null || this.period === undefined) { + this.period = DEFAULT_PERIOD; + } + if (this.period === Period.all) { + return thresholdComments; + } + const currentTime = new Date().getTime(); + let periodInSeconds; + const hourInSeconds = 3_600_000; + switch (this.period) { + case Period.fromNow: + if (!this.fromNow) { + this.fromNow = currentTime; + } + break; + case Period.oneHour: + periodInSeconds = hourInSeconds; + break; + case Period.threeHours: + periodInSeconds = hourInSeconds * 3; + break; + case Period.oneDay: + periodInSeconds = hourInSeconds * 24; + break; + case Period.oneWeek: + periodInSeconds = hourInSeconds * 168; + break; + case Period.twoWeeks: + periodInSeconds = hourInSeconds * 336; + break; + default: + throw new Error('Time period is invalid.'); + } + const filterTime = (this.period === Period.fromNow ? this.fromNow : currentTime - periodInSeconds); + return thresholdComments.filter(c => new Date(c.timestamp).getTime() >= filterTime); + } + + filterCommentsByType(comment: Comment[]): Comment[] { + let filterFunc: (c: Comment) => boolean; + switch (this.filterType) { + case FilterType.correct: + filterFunc = (c) => c.correct === CorrectWrong.CORRECT; + break; + case FilterType.wrong: + filterFunc = (c) => c.correct === CorrectWrong.WRONG; + break; + case FilterType.favorite: + filterFunc = (c) => c.favorite; + break; + case FilterType.bookmark: + filterFunc = (c) => c.bookmark; + break; + case FilterType.read: + filterFunc = (c) => c.read; + break; + case FilterType.unread: + filterFunc = (c) => !c.read; + break; + case FilterType.tag: + filterFunc = (c) => c.tag === this.filterCompare; + break; + case FilterType.userNumber: + filterFunc = (c) => c.userNumber === this.filterCompare; + break; + case FilterType.keyword: + filterFunc = (c) => !!(c.keywordsFromQuestioner?.find(k => k.text === this.filterCompare) || + c.keywordsFromSpacy?.find(k => k.text === this.filterCompare)); + break; + case FilterType.answer: + filterFunc = (c) => !!c.answer; + break; + case FilterType.unanswered: + filterFunc = (c) => !c.answer; + break; + case FilterType.owner: + filterFunc = (c) => c.creatorId === this.userId; + break; + case FilterType.moderator: + filterFunc = (c) => this.moderatorIds.has(c.creatorId); + break; + case FilterType.lecturer: + filterFunc = (c) => c.creatorId === this.ownerId; + break; + default: + return comment; + } + return comment.filter(filterFunc); + } + + sortCommentsBySortType(comments: Comment[]): Comment[] { + let sortFunc: (a: Comment, b: Comment) => number; + switch (this.sortType) { + case SortType.voteasc: + sortFunc = (a, b) => a.score - b.score; + break; + case SortType.votedesc: + sortFunc = (a, b) => b.score - a.score; + break; + case SortType.time: + sortFunc = (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); + break; + } + if (sortFunc) { + comments.sort(sortFunc); + } + comments.sort((a, b) => this.getCommentRoleValue(b) - this.getCommentRoleValue(a)); + return comments; + } + + private getCommentRoleValue(comment: Comment): number { + if (comment.creatorId === this.ownerId) { + return 2; + } else if (this.moderatorIds.has(comment.creatorId)) { + return 1; + } + return 0; + } +} diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts index 6465af45aae2340bb87019aba083f63e19e2a12f..59271f5523ba2450994f66340ef43aba778927cd 100644 --- a/src/app/services/util/room-data.service.ts +++ b/src/app/services/util/room-data.service.ts @@ -13,7 +13,7 @@ import { SpacyKeyword } from '../http/spacy.service'; export interface UpdateInformation { type: 'CommentCreated' | 'CommentPatched' | 'CommentHighlighted' | 'CommentDeleted'; - subtype?: string; + subtype?: (keyof Comment); comment: Comment; finished?: boolean; updates?: (keyof Comment)[]; @@ -100,7 +100,7 @@ export class RoomDataService { private _currentSubscriptions: RoomDataUpdateSubscription[] = []; private _currentComments: Comment[] = null; - private _commentUpdates: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null); + private _currentRoomComments: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null); private _fastCommentAccess: FastRoomAccessObject = null; private _wsCommentServiceSubscription: Subscription = null; private _currentRoomId: string = null; @@ -140,10 +140,10 @@ export class RoomDataService { getRoomData(roomId: string, freezed: boolean = false): Observable<Comment[]> { const tempSubject = new BehaviorSubject<Comment[]>(null); if (this._currentRoomId !== roomId) { - this._commentUpdates.next(null); + this._currentRoomComments.next(null); } let subscription: Subscription = null; - subscription = this._commentUpdates.subscribe(comments => { + subscription = this._currentRoomComments.subscribe(comments => { if (comments === null) { return; } @@ -287,7 +287,7 @@ export class RoomDataService { private triggerUpdate(type: UpdateType, additionalInformation: UpdateInformation) { if (type === UpdateType.force) { - this._commentUpdates.next(this._currentComments); + this._currentRoomComments.next(this._currentComments); } else if (type === UpdateType.commentStream) { for (const subscription of this._currentSubscriptions) { subscription.onUpdate(additionalInformation); diff --git a/src/app/utils/create-comment-keywords.ts b/src/app/utils/create-comment-keywords.ts index 9ba833a870ee2d266fa01a7abe411d47f1c6060c..26cc24f06f3c2928e1fa1a81aac54a39dbe5d477 100644 --- a/src/app/utils/create-comment-keywords.ts +++ b/src/app/utils/create-comment-keywords.ts @@ -91,7 +91,6 @@ export class CreateCommentKeywords { const wordCount = text.trim().split(' ').length; const hasConfidence = selectedLanguage === 'auto' ? result.language.detectedLanguage.confidence >= 0.5 : true; const errorQuotient = (result.matches.length * 100) / wordCount; - console.log(errorQuotient); if (!hasConfidence || errorQuotient > ERROR_QUOTIENT_USE_DEEPL || (!useDeepl && errorQuotient > ERROR_QUOTIENT_WELL_SPELLED)) {