diff --git a/src/app/components/shared/_dialogs/room-create/room-create.component.ts b/src/app/components/shared/_dialogs/room-create/room-create.component.ts index 5c83def0486ec96696029d41e50134b895e0378e..1e49b428fcd067fe53775b70b635676eac133457 100644 --- a/src/app/components/shared/_dialogs/room-create/room-create.component.ts +++ b/src/app/components/shared/_dialogs/room-create/room-create.component.ts @@ -74,6 +74,7 @@ export class RoomCreateComponent implements OnInit { newRoom.name = longRoomName; newRoom.abbreviation = '00000000'; newRoom.description = ''; + newRoom.blacklist = '[]'; if (this.hasCustomShortId && this.customShortIdName && this.customShortIdName.length > 0) { if (!new RegExp('[1-9a-z,A-Z,\s,\-,\.,\_,\~]+').test(this.customShortIdName) || this.customShortIdName.startsWith(' ') || this.customShortIdName.endsWith(' ')) { diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts index 43437d2f46dd6320149bfce02e6b02ccf39310ed..5d0dbabc488342451031004c7321b514b6b9b60d 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts @@ -1,13 +1,58 @@ -export interface TopicCloudAdminData{ +export interface TopicCloudAdminData { blacklist: string[]; + wantedLabels: { + de: string[]; + en: string[]; + }; considerVotes: boolean; profanityFilter: boolean; blacklistIsActive: boolean; keywordORfulltext: KeywordOrFulltext; } -export enum KeywordOrFulltext{ +export enum KeywordOrFulltext { keyword, fulltext, both } + +export interface Label { + readonly tag: string; + readonly label: string; +} + +export class Labels { + readonly de: Label[]; + readonly en: Label[]; + + constructor(_de: Label[], _en: Label[]) { + this.de = _de; + this.en = _en; + } +} + +const deLabels: Label[] = [ + {tag: 'sb', label: 'Subjekt'}, + {tag: 'pd', label: 'Prädikat'}, + {tag: 'og', label: 'Genitivobjekt'}, + {tag: 'ag', label: 'Genitivattribut'}, + {tag: 'app', label: 'Apposition'}, + {tag: 'da', label: 'Dativobjekt'}, + {tag: 'oa', label: 'Akkusativobjekt'}, + {tag: 'nk', label: 'Noun Kernel Element'}, + {tag: 'mo', label: 'Modifikator'}, + {tag: 'cj', label: 'Konjunktor'} +]; + +const enLabels: Label[] = [ + {tag: 'no', label: 'Noun'}, + {tag: 'pro', label: 'Pronoun'}, + {tag: 've', label: 'Verb'}, + {tag: 'adj', label: 'Adjective'}, + {tag: 'adv', label: 'AdverbDVERB'}, + {tag: 'pre', label: 'Preposition'}, + {tag: 'con', label: 'Conjunction'}, + {tag: 'int', label: 'Interjection'} +]; + +export const spacyLabels = new Labels(deLabels, enLabels); diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html index bc7471ca4f14d7ec2bb3e8d56cb80218bd7086de..06aa818ba806b9e31e753813169f82e597594369 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html @@ -11,9 +11,7 @@ <mat-label class="color-on-surface"> {{"topic-cloud-dialog.select-choice" | translate}} </mat-label> - <mat-radio-group - class="radio-button-group" - [(ngModel)]="keywordORfulltext"> + <mat-radio-group class="radio-button-group" [(ngModel)]="keywordORfulltext"> <mat-radio-button checked="true" [value]="keywordOrFulltextENUM[0]" class="radio-button-item"> {{"topic-cloud-dialog.keyword" | translate}} </mat-radio-button> @@ -37,7 +35,7 @@ </mat-slide-toggle> <mat-accordion class="new-profanity-word" multi> - <mat-expansion-panel class="color-background" (opened)="enterProfanityWord=true; focusInput('profanity-word-input')" + <mat-expansion-panel class="color-background" (opened)="enterProfanityWord=true; focusInput('bad-word-input')" (closed)="enterProfanityWord = false"> <mat-expansion-panel-header class="color-background"> <mat-panel-title> @@ -65,12 +63,13 @@ <div> <button mat-raised-button *ngIf="getProfanityList().length > 0" class="primaryBackground" (click)="showProfanityList=!showProfanityList"> - {{showProfanityList ? ('topic-cloud-dialog.hide-profanity-list' | translate) : ('topic-cloud-dialog.show-profanity-list' | translate)}} + {{showProfanityList ? ('topic-cloud-dialog.hide-profanity-list' | translate) : + ('topic-cloud-dialog.show-profanity-list' | translate)}} </button> </div> </mat-expansion-panel> - <mat-expansion-panel class="color-background" + <mat-expansion-panel class="color-background margin-bottom" (opened)="enterBlacklistWord = true; focusInput('blacklist-word-input')" (closed)="enterBlacklistWord = false"> <mat-expansion-panel-header class="color-background"> @@ -87,9 +86,8 @@ <button mat-stroked-button color="primary" class="margin-left" (click)="addBlacklistWord()"> {{'topic-cloud-dialog.add-word' | translate}} </button> - - <mat-list role="list" *ngIf="showBlacklistWordList" class="margin-bottom"> - <mat-list-item class="color-on-surface" *ngFor="let word of getBlacklist()" role="listitem">{{word}} + <mat-list role="list" *ngIf="showBlacklistWordList && blacklist.length > 0" class="margin-bottom"> + <mat-list-item class="color-on-surface" *ngFor="let word of blacklist" role="listitem">{{word}} <button style="margin-left: auto" mat-icon-button class="red" (click)="removeWordFromBlacklist(word)"> <mat-icon mat-list-icon style="margin-bottom: 6px;">delete</mat-icon> </button> @@ -97,12 +95,53 @@ </mat-list> <div> - <button mat-raised-button *ngIf="getBlacklist().length > 0" class="primaryBackground" + <button mat-raised-button class="primaryBackground" *ngIf="blacklist.length > 0" (click)="showBlacklistWordList=!showBlacklistWordList"> - {{showBlacklistWordList ? ('topic-cloud-dialog.hide-blacklist' | translate) : ('topic-cloud-dialog.show-blacklist' | translate)}} + {{showBlacklistWordList ? ('topic-cloud-dialog.hide-blacklist' | translate) : + ('topic-cloud-dialog.show-blacklist' | translate)}} </button> </div> </mat-expansion-panel> + <mat-expansion-panel class="color-background"> + <mat-expansion-panel-header class="color-background"> + <mat-panel-title> + Spacy labels + </mat-panel-title> + </mat-expansion-panel-header> + + <mat-tab-group animationDuration="0ms" mat-stretch-tabs mat-align-tabs="center"> + <mat-tab label="{{'topic-cloud-dialog.german' | translate}}"> + <mat-selection-list [(ngModel)]="wantedLabels.de"> + + <mat-option class="color-on-surface" (click)="selectAllDE(); allSelectedDE = !allSelectedDE"> + <mat-label> + <mat-icon>playlist_add_check</mat-icon> + {{'topic-cloud-dialog.select-all' | translate}} + </mat-label> + </mat-option> + + <mat-list-option [value]="label.tag" class="color-on-surface" *ngFor="let label of spacyLabels.de"> + {{label.label + " (" + label.tag + ")"}} + </mat-list-option> + </mat-selection-list> + </mat-tab> + <mat-tab label="{{'topic-cloud-dialog.english' | translate}}"> + <mat-selection-list [(ngModel)]="wantedLabels.en"> + + <mat-option class="color-on-surface" (click)="selectAllEN(); allSelectedEN = !allSelectedEN"> + <mat-label> + <mat-icon>playlist_add_check</mat-icon> + {{'topic-cloud-dialog.select-all' | translate}} + </mat-label> + </mat-option> + + <mat-list-option [value]="label.tag" class="color-on-surface" *ngFor="let label of spacyLabels.en"> + {{label.label + " (" + label.tag + ")"}} + </mat-list-option> + </mat-selection-list> + </mat-tab> + </mat-tab-group> + </mat-expansion-panel> </mat-accordion> </mat-expansion-panel> </mat-accordion> @@ -123,12 +162,14 @@ </div> <div fxLayoutAlign="center center" style="margin-left: auto; font-weight: bold;"> - <mat-icon [ngClass]="{'animation-blink': searchMode}">sell</mat-icon> - <p class="margin-left" [ngClass]="{'animation-blink': searchMode}">{{searchMode ? filteredKeywords.length : keywords.length}}</p> + <mat-icon svgIcon="comment_tag" + [ngClass]="{'animation-blink': searchMode}" + class="oldtypo-h2 comment_tag-icon"></mat-icon> + <p [ngClass]="{'animation-blink': searchMode}">{{searchMode ? filteredKeywords.length : + keywords.length}}</p> </div> <div class="margin-left vertical-center"> - <button [ngClass]="{'animation-blink': sortMode!=='alphabetic'}" mat-icon-button - [matMenuTriggerFor]="sortMenu"> + <button [ngClass]="{'animation-blink': sortMode!=='alphabetic'}" mat-icon-button [matMenuTriggerFor]="sortMenu"> <mat-icon>sort</mat-icon> </button> </div> @@ -151,14 +192,20 @@ </button> </mat-menu> + <mat-card class="color-surface" *ngIf="keywords.length === 0 || (searchMode && filteredKeywords.length === 0)"> + <p class="color-on-surface" fxLayoutAlign="center"> + {{'topic-cloud-dialog.no-keywords-note' | translate}} + </p> + </mat-card> + <mat-accordion> - <mat-expansion-panel class="color-surface" hideToggle *ngIf="searchMode && filteredKeywords.length === 0"> + <!-- <mat-expansion-panel class="color-surface" hideToggle *ngIf="keywords.length === 0 || (searchMode && filteredKeywords.length === 0)"> <mat-expansion-panel-header class="color-surface"> <mat-panel-title fxLayoutAlign="center"> {{'topic-cloud-dialog.no-keywords-note' | translate}} </mat-panel-title> </mat-expansion-panel-header> - </mat-expansion-panel> + </mat-expansion-panel> --> <mat-expansion-panel class="color-surface" (opened)="panelOpenState = true" (closed)="panelOpenState = edit = false" *ngFor="let keyword of @@ -168,13 +215,13 @@ {{profanityFilter ? getKeywordWithoutProfanity(keyword.keyword) : keyword.keyword}} </mat-panel-title> <mat-panel-description> - {{keyword.questions.length}} - {{'topic-cloud-dialog.question-count-'+(keyword.questions.length > 1 ? 'plural' : 'singular') | translate}} + {{keyword.comments.length}} + {{'topic-cloud-dialog.question-count-'+(keyword.comments.length > 1 ? 'plural' : 'singular') | translate}} </mat-panel-description> </mat-expansion-panel-header> - <div *ngFor="let question of keyword.questions"> + <div *ngFor="let question of keyword.comments"> <mat-divider></mat-divider> - <app-topic-dialog-comment [question]="question" [keyword]="keyword.keyword" [maxShowedCharachters]="140" + <app-topic-dialog-comment [question]="question.body" [keyword]="keyword.keyword" [maxShowedCharachters]="140" [isCollapsed]="!panelOpenState" [profanityFilter]="profanityFilter"></app-topic-dialog-comment> </div> diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss index f44532d248f95cfcc58ff91faad7bb06a4d2e4e3..c283bcb67cc19596c035738be223da967e7815ce 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss @@ -16,6 +16,10 @@ margin-bottom: 16px; } +.margin-top { + margin-top: 16px; +} + .primary { color: var(--primary); background: none; @@ -134,3 +138,6 @@ mat-dialog-content { max-height: 80vh!important; } +.comment_tag-icon { + height: 18px !important; +} diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts index b5b55d9ca573918ab7ab771be1fb8778b82eb7d7..8a76b6ab9cb293ad903f7f753da3afd92638c374 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts @@ -1,15 +1,18 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { TagCloudComponent } from '../../tag-cloud/tag-cloud.component'; +import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { NotificationService } from '../../../../services/util/notification.service'; import { TopicCloudConfirmDialogComponent } from '../topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component'; -import { AuthenticationService } from '../../../../services/http/authentication.service'; import { UserRole } from '../../../../models/user-roles.enum'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../../services/util/language.service'; import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; -import { TopicCloudAdminData } from './TopicCloudAdminData'; +import { TopicCloudAdminData, Labels, spacyLabels } from './TopicCloudAdminData'; import { KeywordOrFulltext } from './TopicCloudAdminData'; +import { User } from '../../../../models/user'; +import { Comment } from '../../../../models/comment'; +import { CommentService } from '../../../../services/http/comment.service'; +import { WsCommentServiceService } from '../../../../services/websockets/ws-comment-service.service'; +import { TSMap } from 'typescript-map'; @Component({ selector: 'app-topic-cloud-administration', @@ -21,6 +24,8 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { public considerVotes: boolean; public profanityFilter: boolean; public blacklistIsActive: boolean; + blacklist: string[] = []; + blacklistSubscription = undefined; keywordOrFulltextENUM = KeywordOrFulltext; newKeyword = undefined; edit = false; @@ -38,95 +43,91 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { showSettingsPanel = false; keywordORfulltext: string = undefined; userRole: UserRole; - keywords: Keyword[] = [ - { - keywordID: 1, - keyword: 'Cloud', - questions: [ - 'Wieviel speicherplatz steht mir in der Cloud zur verfügung?', - 'Sollen wir die Tag Cloud implementieren?', - // eslint-disable-next-line max-len - 'Wie genau ist die Cloud aufgebaut? Wieviel speicherplatz steht mir in der Cloud zur verfuegungWie genau ist die Cloud aufgebaut? Wieviel speicherplatz steht mir in der Cloud zur verfuegungWie genau ist die Cloud aufgebaut? Wieviel speicherplatz steht mir in der Cloud zur verfuegungWie genau ist die Cloud aufgebaut? Wieviel speicherplatz steht mir in der Cloud zur verfuegungWie genau ist die Cloud aufgebaut? Wieviel speicherplatz steht mir in der Cloud zur verfuegungWie genau ist die Cloud aufgebaut? Wieviel speicherplatz steht mir in der Cloud zur verfuegung', - ] - }, - { - keywordID: 2, - keyword: 'SWT', - questions: [ - 'Muss man fuer das Modul SWT bestanden haben?' - ] - }, - { - keywordID: 3, - keyword: 'Frage', - questions: [ - 'Das ist eine Lange Frage mit dem Thema \'frage\'', - 'Ich habe eine Frage, sind Fragen zum thema \'Frage\' auch erlaubt?', - 'Ich wollte Fragen ob sie gerne Sachen gefragt werden', - 'Langsam geht mir die Fragerei mit den ganzen Fragen auf den Geist Frage' - ] - }, - { - keywordID: 4, - keyword: 'Klausur', - questions: [ - 'Darf man in der Klausur hilfmittel verwenden?', - 'An welchem Termin findet die Klausur statt?' - ] - }, - { - keywordID: 5, - keyword: 'Diskrete Math', - questions: [ - 'wann wird die nächste veranstaltung stattfinden?', - 'gibt es heute übung?' - ] - }, - { - keywordID: 6, - keyword: 'Arsch', - questions: [ - 'Das ist eine Testfrage fuer den Profanity Filter, du Arschloch', - 'Englisch: Fuck you!', - 'Deutsch: Fick dich!', - 'Französisch: Gros con!', - 'Türkisch: Orospu çocuğu!', - 'Arabisch: عاهرة!', - 'Russisch: Муда!', - 'Multi language: Ficken, Fuck, con', - 'Custom: Nieder mit KQC' - ] - }, - ]; + + @ViewChild('allSelectedDE') allSelectedDE2; + // @ViewChild('allSelectedEN') allSelectedEN; + allSelectedDE = true; + allSelectedEN = true; + spacyLabels: Labels; + wantedLabels: { + de: string[]; + en: string[]; + }; + + keywords: Keyword[] = []; private topicCloudAdminData: TopicCloudAdminData; - constructor(public cloudDialogRef: MatDialogRef<TagCloudComponent>, + constructor( + @Inject(MAT_DIALOG_DATA) public data: Data, + public cloudDialogRef: MatDialogRef<TopicCloudAdministrationComponent>, public confirmDialog: MatDialog, private notificationService: NotificationService, - private authenticationService: AuthenticationService, private translateService: TranslateService, private langService: LanguageService, - private topicCloudAdminService: TopicCloudAdminService) { + private topicCloudAdminService: TopicCloudAdminService, + private commentService: CommentService, + private wsCommentServiceService: WsCommentServiceService) { this.langService.langEmitter.subscribe(lang => { this.translateService.use(lang); }); } ngOnInit(): void { + this.wsCommentServiceService.getCommentStream(localStorage.getItem('roomId')).subscribe(_ => this.initKeywords()); + this.blacklistSubscription = this.topicCloudAdminService.getBlacklist().subscribe(list => this.blacklist = list); + this.isCreatorOrMod = this.data ? (this.data.user.role !== UserRole.PARTICIPANT) : true; this.translateService.use(localStorage.getItem('currentLang')); - this.checkIfUserIsModOrCreator(); - this.checkIfThereAreQuestions(); - this.sortQuestions(); + this.spacyLabels = spacyLabels; + this.wantedLabels = undefined; this.setDefaultAdminData(); + this.initKeywords(); } ngOnDestroy(){ this.setAdminData(); + if(this.blacklistSubscription !== undefined){ + this.blacklistSubscription.unsubscribe(); + } + } + + initKeywords(){ + this.commentService.getFilteredComments(localStorage.getItem('roomId')).subscribe(comments => { + this.keywords = []; + comments.map(comment => { + const keywords = this.keywordORfulltext === KeywordOrFulltext[0] ? comment.keywordsFromQuestioner : comment.keywordsFromSpacy; + keywords.map(_keyword => { + const existingKey = this.checkIfKeywordExists(_keyword); + if (existingKey){ + existingKey.vote += comment.score; + if (this.checkIfCommentExists(existingKey.comments, comment.id)){ + existingKey.comments.push(comment); + } + } else { + const keyword: Keyword = { + keyword: _keyword, + comments: [comment], + vote: comment.score + }; + this.keywords.push(keyword); + } + }); + }); + // this.checkIfThereAreQuestions(); + this.sortQuestions(); + }); + } + + checkIfCommentExists(comments: Comment[], id: string): boolean{ + return comments.filter(comment => comment.id === id).length === 0; } - setAdminData(){ + setAdminData() { this.topicCloudAdminData = { - blacklist: this.topicCloudAdminService.getBlacklistWords(this.profanityFilter, this.blacklistIsActive), + blacklist: [], + wantedLabels: { + de: this.wantedLabels.de, + en: this.wantedLabels.en + }, considerVotes: this.considerVotes, profanityFilter: this.profanityFilter, blacklistIsActive: this.blacklistIsActive, @@ -136,12 +137,16 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } setDefaultAdminData() { - this.topicCloudAdminData = this.topicCloudAdminService.getAdminData; + this.topicCloudAdminData = this.topicCloudAdminService.getDefaultAdminData; if (this.topicCloudAdminData) { this.considerVotes = this.topicCloudAdminData.considerVotes; this.profanityFilter = this.topicCloudAdminData.profanityFilter; this.blacklistIsActive = this.topicCloudAdminData.blacklistIsActive; this.keywordORfulltext = KeywordOrFulltext[this.topicCloudAdminData.keywordORfulltext]; + this.wantedLabels = { + de: this.topicCloudAdminData.wantedLabels.de, + en: this.topicCloudAdminData.wantedLabels.en + }; } } @@ -150,11 +155,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } getProfanityList() { - return this.topicCloudAdminService.getProfanityList(); - } - - getBlacklist() { - return this.topicCloudAdminService.getBlacklist(); + return this.topicCloudAdminService.getCustomProfanityList(); } sortQuestions(sortMode?: string) { @@ -167,27 +168,22 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.keywords.sort((a, b) => a.keyword.localeCompare(b.keyword)); break; case 'questionsCount': - this.keywords.sort((a, b) => b.questions.length - a.questions.length); + this.keywords.sort((a, b) => b.comments.length - a.comments.length); break; case 'voteCount': - console.log('not implemented!, sorting with question count'); - this.keywords.sort((a, b) => b.questions.length - a.questions.length); + this.keywords.sort((a, b) => b.vote - a.vote); break; } } - checkIfUserIsModOrCreator() { - this.isCreatorOrMod = this.authenticationService.getRole() === UserRole.CREATOR || - this.authenticationService.getRole() === UserRole.EDITING_MODERATOR || - this.authenticationService.getRole() === UserRole.EXECUTIVE_MODERATOR; - } - checkIfThereAreQuestions() { if (this.keywords.length === 0){ - this.translateService.get('topic-cloud-dialog.nokeyword-note').subscribe(msg => { + this.translateService.get('topic-cloud-dialog.no-keywords-note').subscribe(msg => { this.notificationService.show(msg); }); - this.cloudDialogRef.close(); + setTimeout(() => { + this.cloudDialogRef.close(); + }, 0); } } @@ -198,38 +194,68 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { }, 0); } - deleteKeyword(key: Keyword): void{ - this.keywords.map(keyword => { - if (keyword.keywordID === key.keywordID) { - this.keywords.splice(this.keywords.indexOf(keyword, 0), 1); - } + deleteKeyword(key: Keyword, message?: string): void{ + key.comments.map(comment => { + const changes = new TSMap<string, any>(); + let keywords = comment.keywordsFromQuestioner; + keywords.splice(keywords.indexOf(key.keyword, 0), 1); + changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); + keywords = comment.keywordsFromSpacy; + keywords.splice(keywords.indexOf(key.keyword, 0), 1); + changes.set('keywordsFromSpacy', JSON.stringify(keywords)); + this.updateComment(comment, changes, message); }); - if (this.keywords.length === 0) { - this.cloudDialogRef.close(); - } + if (this.searchMode === true){ - /* update filtered array if it is searchmode */ this.searchKeyword(); } } + updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string){ + this.commentService.patchComment(updatedComment, changes).subscribe(_ => { + if (messageTranslate){ + this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { + this.notificationService.show(msg); + }); + } + }, + error => { + this.translateService.get('topic-cloud-dialog.changes-gone-wrong').subscribe(msg => { + this.notificationService.show(msg); + }); + }); + } + cancelEdit(): void { this.edit = false; this.newKeyword = undefined; } confirmEdit(key: Keyword): void { - for (const keyword of this.keywords){ - if (keyword.keywordID === key.keywordID) { - const key2 = this.checkIfKeywordExists(this.newKeyword.trim().toLowerCase()); - if (key2){ - this.openConfirmDialog('merge-message', 'merge', keyword, key2); - } else { - keyword.keyword = this.newKeyword.trim(); + const key2 = this.checkIfKeywordExists(this.newKeyword); + if (key2){ + this.openConfirmDialog('merge-message', 'merge', key, key2); + } else { + key.comments.map(comment => { + const changes = new TSMap<string, any>(); + let keywords = comment.keywordsFromQuestioner; + for (let i = 0; i < keywords.length; i++){ + if (keywords[i].toLowerCase() === key.keyword.toLowerCase()){ + keywords[i] = this.newKeyword.trim(); + } } - break; - } + changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); + keywords = comment.keywordsFromSpacy; + for (let i = 0; i < keywords.length; i++){ + if (keywords[i].toLowerCase() === key.keyword.toLowerCase()){ + keywords[i] = this.newKeyword.trim(); + } + } + changes.set('keywordsFromSpacy', JSON.stringify(keywords)); + this.updateComment(comment, changes, 'keyword-edit'); + }); } + this.edit = false; this.newKeyword = undefined; this.sortQuestions(); @@ -246,7 +272,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { confirmDialogRef.afterClosed().subscribe(result => { if (result === 'delete') { - this.deleteKeyword(keyword); + this.deleteKeyword(keyword, 'keyword-delete'); } else if (result === 'merge') { this.mergeKeywords(keyword, mergeTarget); } @@ -266,8 +292,17 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { mergeKeywords(key1: Keyword, key2: Keyword) { if (key1 !== undefined && key2 !== undefined){ - key1.questions.map(question => { - key2.questions.push(question); + key1.comments.map(comment => { + if (this.checkIfCommentExists(key2.comments, comment.id)){ + const changes = new TSMap<string, any>(); + let keywords = comment.keywordsFromQuestioner; + keywords.push(key2.keyword); + changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); + keywords = comment.keywordsFromSpacy; + keywords.push(key2.keyword); + changes.set('keywordsFromSpacy', JSON.stringify(keywords)); + this.updateComment(comment, changes); + } }); this.deleteKeyword(key1); } @@ -275,7 +310,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { checkIfKeywordExists(key: string): Keyword { for(const keyword of this.keywords){ - if(keyword.keyword.toLowerCase() === key){ + if(keyword.keyword.toLowerCase() === key.trim().toLowerCase()){ return keyword; } } @@ -297,7 +332,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } addBlacklistWord() { - this.topicCloudAdminService.addToBlacklistWordList(this.newBlacklistWord); + this.topicCloudAdminService.addWordToBlacklist(this.newBlacklistWord); this.newBlacklistWord = undefined; if (this.searchMode){ this.searchKeyword(); @@ -312,13 +347,40 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.topicCloudAdminService.removeWordFromBlacklist(word); } - refreshAllLists(){ + refreshAllLists() { this.searchKeyword(); } + + selectAllDE() { + if (this.allSelectedDE) { + this.wantedLabels.de = [] + } else { + this.wantedLabels.de = []; + this.spacyLabels.de.forEach(label => { + this.wantedLabels.de.push(label.tag); + }); + } + } + + selectAllEN() { + if (this.allSelectedEN) { + this.wantedLabels.en = []; + this.spacyLabels.en.forEach(label => { + this.wantedLabels.en.push(label.tag); + }); + } else { + this.wantedLabels.en = [] + } + } } interface Keyword { - keywordID: number; keyword: string; - questions: string[]; + comments: Comment[]; + vote: number; } + +export interface Data{ + user: User; +} + diff --git a/src/app/models/room.ts b/src/app/models/room.ts index e83d37ce5b7980f6243cc482754bf1876d594211..1eaaa39ff1471e370993f4eaf2d317e8a86e953b 100644 --- a/src/app/models/room.ts +++ b/src/app/models/room.ts @@ -8,6 +8,7 @@ export class Room { abbreviation: string; name: string; description: string; + blacklist: string; closed: boolean; moderated: boolean; directSend: boolean; @@ -20,6 +21,7 @@ export class Room { abbreviation: string = '', name: string = '', description: string = '', + blacklist: string = '[]', closed: boolean = false, moderated: boolean = true, directSend: boolean = true, @@ -32,6 +34,7 @@ export class Room { this.abbreviation = abbreviation; this.name = name; this.description = description; + this.blacklist = blacklist; this.closed = closed; this.moderated = moderated; this.directSend = directSend; diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index 401b26c6e01d5f684f0b305f03fbcd0abddda2d3..fdecfe2f88e79ef25650ac1890c6028c3dfa424e 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -1,45 +1,49 @@ -import { stringify } from '@angular/compiler/src/util'; import { Injectable } from '@angular/core'; import * as BadWords from 'naughty-words'; // eslint-disable-next-line max-len -import { TopicCloudAdminData, KeywordOrFulltext } from '../../../app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; +import { TopicCloudAdminData, KeywordOrFulltext, Labels, spacyLabels } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; +import { RoomService } from './../../services/http/room.service'; +import { Room } from '../../models/room'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationService } from './notification.service'; +import { Observable, Subject } from 'rxjs'; + @Injectable({ providedIn: 'root', }) export class TopicCloudAdminService { - private badWords = []; + private adminData: Subject<TopicCloudAdminData>; + private blacklist: Subject<string[]>; private profanityWords = []; - private blacklist = []; // should be stored in backend - private profanityKey = 'custom-Profanity-List'; - - constructor() { - this.badWords = BadWords; + private readonly profanityKey = 'custom-Profanity-List'; + private readonly adminKey = 'Topic-Cloud-Admin-Data'; + constructor(private roomService: RoomService, + private translateService: TranslateService, + private notificationService: NotificationService) { + this.blacklist = new Subject<string[]>(); + this.adminData = new Subject<TopicCloudAdminData>(); /* put all arrays of languages together */ - this.profanityWords = this.badWords['en'] - .concat(this.badWords['de']) - .concat(this.badWords['fr']) - .concat(this.badWords['ar']) - .concat(this.badWords['ru']) - .concat(this.badWords['tr']); - } - - getBlacklistWords(profanityFilter: boolean, blacklistFilter: boolean) { - let words = []; - if (profanityFilter) { - // TODO: send only words that are contained in keywords - words = words.concat(this.profanityWords).concat(this.getProfanityList()); - } - if (blacklistFilter && this.blacklist.length > 0) { - words = words.concat(this.blacklist); - } - return words; + this.profanityWords = BadWords['en'] + .concat(BadWords['de']) + .concat(BadWords['fr']) + .concat(BadWords['ar']) + .concat(BadWords['ru']) + .concat(BadWords['tr']); + } + + get getAdminData(): Observable<TopicCloudAdminData>{ + return this.adminData.asObservable(); } - get getAdminData(): TopicCloudAdminData { - let data = JSON.parse(localStorage.getItem('Topic-Cloud-Admin-Data')); + get getDefaultAdminData(): TopicCloudAdminData { + let data = JSON.parse(localStorage.getItem(this.adminKey)); if (!data) { data = { - blacklist: this.profanityWords, + blacklist: [], + wantedLabelsDE: { + de: this.getDefaultSpacyTagsDE(), + en: this.getDefaultSpacyTagsEN() + }, considerVotes: false, profanityFilter: true, blacklistIsActive: false, @@ -49,13 +53,25 @@ export class TopicCloudAdminService { return data; } - setAdminData(adminData: TopicCloudAdminData){ - localStorage.setItem('Topic-Cloud-Admin-Data', JSON.stringify(adminData)); + setAdminData(_adminData: TopicCloudAdminData) { + localStorage.setItem(this.adminKey, JSON.stringify(_adminData)); + this.getBlacklist().subscribe(list => { + _adminData.blacklist = this.getCustomProfanityList().concat(list).concat(this.profanityWords); + this.adminData.next(_adminData); + }); + } + + getBlacklist(): Observable<string[]> { + // TODO: add watcher for another moderators + this.getRoom().subscribe(room => { + this.blacklist.next(JSON.parse(room.blacklist)); + }); + return this.blacklist.asObservable(); } filterProfanityWords(str: string): string { let questionWithProfanity = str; - this.profanityWords.concat(this.getProfanityList()).map((word) => { + this.profanityWords.concat(this.getCustomProfanityList()).map((word) => { questionWithProfanity = questionWithProfanity .toLowerCase() .includes(word.toLowerCase()) @@ -69,15 +85,15 @@ export class TopicCloudAdminService { return questionWithProfanity; } - getProfanityList(): string[] { + getCustomProfanityList(): string[] { const list = localStorage.getItem(this.profanityKey); return list ? list.split(',') : []; } addToProfanityList(word: string) { if (word !== undefined) { - const newList = this.getProfanityList(); - if (newList.includes(word)){ + const newList = this.getCustomProfanityList(); + if (newList.includes(word)) { return; } newList.push(word); @@ -86,33 +102,80 @@ export class TopicCloudAdminService { } removeFromProfanityList(profanityWord: string) { - const list = this.getProfanityList(); + const list = this.getCustomProfanityList(); list.map(word => { - if (word === profanityWord){ + if (word === profanityWord) { list.splice(list.indexOf(word, 0), 1); } }); localStorage.setItem(this.profanityKey, list.toString()); } - removeProfanityList(){ + removeProfanityList() { localStorage.removeItem(this.profanityKey); } - getBlacklist(): string[] { - return this.blacklist; + getRoom(): Observable<Room> { + return this.roomService.getRoom(localStorage.getItem('roomId')); } - addToBlacklistWordList(word: string) { + addWordToBlacklist(word: string) { if (word !== undefined) { - this.blacklist.push(word); + this.getRoom().subscribe(room => { + const newlist = JSON.parse(room.blacklist); + newlist.push(word); + this.updateBlacklist(newlist, room); + }); } } removeWordFromBlacklist(word: string) { - this.blacklist.splice(this.blacklist.indexOf(word), 1); + if (word !== undefined) { + this.getRoom().subscribe(room => { + if (room.blacklist.length > 0){ + const newlist = JSON.parse(room.blacklist); + newlist.splice(newlist.indexOf(word, 0), 1); + this.updateBlacklist(newlist, room); + } + }); + } + } + + updateBlacklist(list: string[], room: Room){ + room.blacklist = JSON.stringify(list); + this.updateRoom(room); + } + + updateRoom(updatedRoom: Room){ + this.roomService.updateRoom(updatedRoom).subscribe(_ => { + this.translateService.get('topic-cloud.changes-successful').subscribe(msg => { + this.notificationService.show(msg); + /* update blacklist for subscribers */ + this.blacklist.next(JSON.parse(updatedRoom.blacklist)); + }); + }, + error => { + this.translateService.get('topic-cloud.changes-gone-wrong').subscribe(msg => { + this.notificationService.show(msg); + }); + }); } + getDefaultSpacyTagsDE(): string[] { + let tags: string[]; + spacyLabels.de.forEach(label => { + tags.push(label.tag); + }); + return tags; + } + + getDefaultSpacyTagsEN(): string[] { + let tags: string[]; + spacyLabels.en.forEach(label => { + tags.push(label.tag); + }); + return tags; + } private replaceString(str: string, search: string, replace: string) { return str.split(search).join(replace); diff --git a/src/assets/i18n/home/de.json b/src/assets/i18n/home/de.json index 0d824a82c8f071e9a85578d32e4f12cc5ae47e93..e54b40b66a45a4530e80e9aa144bb5ef949697aa 100644 --- a/src/assets/i18n/home/de.json +++ b/src/assets/i18n/home/de.json @@ -311,5 +311,9 @@ }, "qr-dialog": { "session": "Raum" + }, + "topic-cloud": { + "changes-gone-wrong": "Etwas ist schief gelaufen!", + "changes-successful": "Änderungen gespeichert." } } diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json index af3295e5a159ffdcd041c04bee2281fcc2d146d4..b886992ec32e98181f3c3a17c4d455cb38749ba4 100644 --- a/src/assets/i18n/home/en.json +++ b/src/assets/i18n/home/en.json @@ -315,5 +315,9 @@ }, "qr-dialog": { "session": "Key code" + }, + "topic-cloud": { + "changes-gone-wrong": "Something went wrong!", + "changes-successful": "Successfully updated." } } diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 38e15dac7460deeeeeb0223520ef9774bed14f5b..c22c7c8276b6f5416f6ab3cdb221d6a2aac2a291 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -263,7 +263,14 @@ "show-blacklist": "Zeige Blackliste", "hide-blacklist": "Verberge Blackliste", "show-profanity-list": "Zeige Schimpfwortliste", - "hide-profanity-list": "Verberge Schimpfwortliste" + "hide-profanity-list": "Verberge Schimpfwortliste", + "keyword-delete": "Stichwort gelöscht", + "keyword-edit": "Stichwort umbenannt", + "keywords-merge": "Stichwörter zusammengefügt", + "changes-gone-wrong": "Etwas ist schiefgelaufen", + "english": "Englisch", + "german": "Deutsch", + "select-all": "Alle auswählen" }, "topic-cloud-confirm-dialog": { "cancel": "Abbrechen", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 4e481147a7f80aa5276cdb9fba94fd701642b0c0..07df33bb3518eb767082a48c8e43a716f01e4b61 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -268,7 +268,14 @@ "show-blacklist": "Show blacklist", "hide-blacklist": "Hide blacklist", "show-profanity-list": "Show profanity list", - "hide-profanity-list": "Hide profanity list" + "hide-profanity-list": "Hide profanity list", + "keyword-delete": "keyword deleted", + "keyword-edit": "keyword renamed", + "keywords-merge": "keywords merged", + "changes-gone-wrong": "somthing has gone wrong", + "english": "English", + "german": "German", + "select-all": "Select all" }, "topic-cloud-confirm-dialog":{ "cancel": "Cancel",