diff --git a/package-lock.json b/package-lock.json index 256b718aece24c323cc7b511db6458e4a699ff5a..4f6b2d7b015c57eac46467561da62468d4c91fa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4113,9 +4113,9 @@ "dev": true }, "clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", + "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", "optional": true, "requires": { "good-listener": "^1.2.2", diff --git a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.ts b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.ts index a8faa57be66af7fac92f41149cea1c4dcf0ba566..150aa70e3fb88fa7e2c3c0a09afcf6b4f90a6650 100644 --- a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.ts +++ b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.ts @@ -23,7 +23,6 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { comment: Comment; commentLang: Model; commentBodyChecked: string; - keywords: Keyword[] = []; keywordsOriginal: Keyword[] = []; @@ -47,7 +46,7 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { /** * Returns a lambda which closes the dialog on call. */ - buildCloseDialogActionCallback(): () => void { + buildCloseDialogActionCallback(): () => void { return () => this.dialogRef.close(); } @@ -60,7 +59,7 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { } evalInput(model: Model) { - const words: Keyword[] = []; + const keywords: Keyword[] = []; let regex; if(this.commentLang === 'de') { regex = new RegExp('(?!Der|Die|Das)[A-ZAÄÖÜ][a-zäöüß]+(-[A-Z][a-zäöüß]+)*', 'g'); @@ -69,41 +68,37 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { } else { regex = new RegExp('(?!au|de|la|le|en|un)[A-ZÀ-Ÿ]{2,}', 'gi'); } - console.log('comLang: ' + this.commentLang); // N at first pos = all Nouns(NN de/en) including singular(NN, NNP en), plural (NNPS, NNS en), proper Noun(NNE, NE de) - this.spacyService.analyse(this.commentBodyChecked, model) - .subscribe(res => { - console.log(res.words); - for(const word of res.words) { - if (word.tag.charAt(0) === 'N') { - const filteredwords = word.text.match(regex); - for (const a of filteredwords) { - if(a !== null && a !== undefined && words.filter(item => item.word === a).length < 1) { - words.push({ - word: a, - completed: false, - editing: false, - selected: false - }); - } + this.spacyService.getKeywords(this.commentBodyChecked, model) + .subscribe(words => { + for(const word of words) { + const filteredwords = word.match(regex) || []; + for (const filteredword of filteredwords) { + if(filteredword !== null && filteredword !== undefined && keywords.filter(item => item.word === filteredword).length < 1) { + keywords.push({ + word: filteredword, + completed: false, + editing: false, + selected: false + }); } } } - this.keywords = words; - this.keywordsOriginal = words; + this.keywords = keywords; + this.keywordsOriginal = keywords; }, () => { this.keywords = []; this.keywordsOriginal = []; }); } - onEdit(keyword){ + onEdit(keyword) { keyword.editing = true; keyword.completed = false; keyword.selected = false; } - onEndEditing(keyword){ + onEndEditing(keyword) { keyword.editing = false; keyword.completed = true; keyword.selected = true; diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts new file mode 100644 index 0000000000000000000000000000000000000000..43437d2f46dd6320149bfce02e6b02ccf39310ed --- /dev/null +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts @@ -0,0 +1,13 @@ +export interface TopicCloudAdminData{ + blacklist: string[]; + considerVotes: boolean; + profanityFilter: boolean; + blacklistIsActive: boolean; + keywordORfulltext: KeywordOrFulltext; +} + +export enum KeywordOrFulltext{ + keyword, + fulltext, + both +} 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 8c9066e9b8fae24d45d6329f65ef020b33273a0f..bc7471ca4f14d7ec2bb3e8d56cb80218bd7086de 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 @@ -1,118 +1,167 @@ <mat-dialog-content> - <mat-card class="color-surface"> - <div> + <mat-accordion hideToggle> + <mat-expansion-panel class="color-surface"> + <mat-expansion-panel-header class="color-surface"> + <mat-icon class="color-on-surface">settings</mat-icon> + <mat-panel-title> + {{'topic-cloud-dialog.settings' | translate}} + </mat-panel-title> + </mat-expansion-panel-header> + <mat-card style="background: none; margin-bottom: 10px;"> + <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-button checked="true" [value]="keywordOrFulltextENUM[0]" class="radio-button-item"> + {{"topic-cloud-dialog.keyword" | translate}} + </mat-radio-button> + <mat-radio-button [value]="keywordOrFulltextENUM[1]" class="radio-button-item"> + {{"topic-cloud-dialog.full-text" | translate}} + </mat-radio-button> + <mat-radio-button [value]="keywordOrFulltextENUM[2]" class="color-on-surface"> + {{"topic-cloud-dialog.both" | translate}} + </mat-radio-button> + </mat-radio-group> + </mat-card> + <mat-slide-toggle [(ngModel)]="considerVotes"> {{'topic-cloud-dialog.consider-votes' | translate}} </mat-slide-toggle> <mat-slide-toggle (change)="refreshAllLists()" [(ngModel)]="profanityFilter"> {{'topic-cloud-dialog.profanity' | translate}} </mat-slide-toggle> - <mat-slide-toggle [(ngModel)]="hideIrrelevant"> - {{'topic-cloud-dialog.hide-irrelevant' | translate}} + <mat-slide-toggle [(ngModel)]="blacklistIsActive"> + {{'topic-cloud-dialog.hide-blacklist-words' | translate}} </mat-slide-toggle> - <mat-accordion hideToggle class="new-Badword" multi> - <mat-expansion-panel class="color-background" - (opened)="enterBadword = true; focusBadWordInput()" - (closed)="enterBadword = false"> + <mat-accordion class="new-profanity-word" multi> + <mat-expansion-panel class="color-background" (opened)="enterProfanityWord=true; focusInput('profanity-word-input')" + (closed)="enterProfanityWord = false"> <mat-expansion-panel-header class="color-background"> - <mat-panel-description> - {{'topic-cloud-dialog.add-profanity-word' | translate}} - <mat-icon>{{!enterBadword ? 'add' : 'remove'}}</mat-icon> - </mat-panel-description> + <mat-panel-title> + {{'topic-cloud-dialog.edit-profanity-list' | translate}} + </mat-panel-title> </mat-expansion-panel-header> <mat-form-field> <mat-label>{{'topic-cloud-dialog.enter-word' | translate}}</mat-label> - <input matInput id="bad-word-input" [(ngModel)]="newBadWord"> + <input matInput id="bad-word-input" [(ngModel)]="newProfanityWord"> </mat-form-field> - <button mat-button color="primary" (click)="addBadword()"> - <mat-icon>add_circle</mat-icon> - {{'topic-cloud-dialog.send' | translate}} + <button mat-stroked-button color="primary" class="margin-left" (click)="addProfanityWord()"> + {{'topic-cloud-dialog.add-word' | translate}} </button> + + <mat-list role="list" *ngIf="showProfanityList" class="margin-bottom"> + <mat-list-item class="color-on-surface" *ngFor="let word of getProfanityList()" role="listitem">{{word}} + <button style="margin-left: auto" mat-icon-button class="red" (click)="removeWordFromProfanityList(word)"> + <mat-icon mat-list-icon style="margin-bottom: 6px;">delete</mat-icon> + </button> + </mat-list-item> + </mat-list> + + <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)}} + </button> + </div> </mat-expansion-panel> - <!-- </mat-accordion> - <mat-accordion hideToggle class="new-Badword" multi> --> <mat-expansion-panel class="color-background" - (opened)="enterIrrelevantWord = true; focusIrrelevantWordInput()" - (closed)="enterIrrelevantWord = false"> + (opened)="enterBlacklistWord = true; focusInput('blacklist-word-input')" + (closed)="enterBlacklistWord = false"> <mat-expansion-panel-header class="color-background"> - <mat-panel-description> - {{'topic-cloud-dialog.add-irrelevant-word' | translate}} - <mat-icon>{{!enterIrrelevantWord ? 'add' : 'remove'}}</mat-icon> - </mat-panel-description> + <mat-panel-title> + {{'topic-cloud-dialog.edit-blacklist-list' | translate}} + </mat-panel-title> </mat-expansion-panel-header> <mat-form-field> <mat-label>{{'topic-cloud-dialog.enter-word' | translate}}</mat-label> - <input matInput id="irrelevant-word-input" [(ngModel)]="newIrrelevantWord"> + <input matInput id="blacklist-word-input" [(ngModel)]="newBlacklistWord"> </mat-form-field> - <button mat-button color="primary" (click)="addIrrelevantWord()"> - <mat-icon>add_circle</mat-icon> - {{'topic-cloud-dialog.send' | translate}} + <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}} + <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> + </mat-list-item> + </mat-list> + + <div> + <button mat-raised-button *ngIf="getBlacklist().length > 0" class="primaryBackground" + (click)="showBlacklistWordList=!showBlacklistWordList"> + {{showBlacklistWordList ? ('topic-cloud-dialog.hide-blacklist' | translate) : ('topic-cloud-dialog.show-blacklist' | translate)}} + </button> + </div> </mat-expansion-panel> </mat-accordion> + </mat-expansion-panel> + </mat-accordion> - </div> - </mat-card> - - <div fxLayout="row" style="margin-top: 10px;"> + <div fxLayout="row"> <mat-label fxLayoutAlign="center center"> <mat-icon>search</mat-icon> </mat-label> - <div style="margin-left: 10px;"> + <div style="margin-left: 10px; margin-top: 6px;"> <mat-form-field [ngClass]="{'search': searchMode}"> - <input #searchBox id="searchBox" (input)="searchKeyword()" - [(ngModel)]="searchedKeyword" matInput type="text" - placeholder="{{'topic-cloud-dialog.keyword-search' | translate}}"> - <button *ngIf="searchedKeyword" - (click)="searchedKeyword = ''; searchMode = false;" - mat-button matSuffix mat-icon-button aria-label="topic-cloud-dialog.keyword-search"> + <input #searchBox class="searchBox" (input)="searchKeyword()" [(ngModel)]="searchedKeyword" matInput type="text" + placeholder="{{'topic-cloud-dialog.keyword-search' | translate}}"> + <button *ngIf="searchedKeyword" (click)="searchedKeyword = ''; searchMode = false;" mat-button matSuffix + mat-icon-button aria-label="topic-cloud-dialog.keyword-search"> <mat-icon>close</mat-icon> </button> - </mat-form-field> </div> - <div fxLayoutAlign="center center" style="margin-left: 52%; font-weight: bold;" > - <p [ngClass]="{'animation-blink': searchMode}">{{searchMode ? filteredKeywords.length : keywords.length}}</p> + + <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> </div> - <div fxLayoutAlign="end" *ngIf="!searchMode"> - <button [ngClass]="{'animation-blink': sortMode!=='alphabetic'}" mat-button [matMenuTriggerFor]="sortMenu" mat-ripple> + <div class="margin-left vertical-center"> + <button [ngClass]="{'animation-blink': sortMode!=='alphabetic'}" mat-icon-button + [matMenuTriggerFor]="sortMenu"> <mat-icon>sort</mat-icon> </button> </div> </div> <mat-menu #sortMenu> - <button [ngClass]="{'animation-blink': sortMode==='alphabetic'}" mat-menu-item (click)="sortQuestions('alphabetic')"> + <button [ngClass]="{'animation-blink': sortMode==='alphabetic'}" mat-menu-item + (click)="sortQuestions('alphabetic')"> <mat-icon>sort_by_alpha</mat-icon> - {{'topic-cloud-dialog.sort-alpha' | translate}} </button> - <button [ngClass]="{'animation-blink': sortMode==='questionsCount'}" mat-menu-item (click)="sortQuestions('questionsCount')"> + {{'topic-cloud-dialog.sort-alpha' | translate}} + </button> + <button [ngClass]="{'animation-blink': sortMode==='questionsCount'}" mat-menu-item + (click)="sortQuestions('questionsCount')"> <mat-icon>swap_vert</mat-icon> - {{'topic-cloud-dialog.sort-count' | translate}} </button> + {{'topic-cloud-dialog.sort-count' | translate}} + </button> <button [ngClass]="{'animation-blink': sortMode==='voteCount'}" mat-menu-item (click)="sortQuestions('voteCount')"> <mat-icon>swap_vert</mat-icon> - {{'topic-cloud-dialog.sort-vote' | translate}} </button> + {{'topic-cloud-dialog.sort-vote' | translate}} + </button> </mat-menu> <mat-accordion> <mat-expansion-panel class="color-surface" hideToggle *ngIf="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> - <!-- *ngFor="let keyword of (searchMode ? filteredKeywords : keywords); let i = index" [attr.data-index]="i"> - --> - <mat-expansion-panel class="color-surface" - (opened)="panelOpenState = true" - (closed)="panelOpenState = edit = false" - *ngFor="let keyword of + <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 class="color-surface" (opened)="panelOpenState = true" (closed)="panelOpenState = edit = false" + *ngFor="let keyword of (searchMode ? filteredKeywords : keywords); let i = index" [attr.data-index]="i"> <mat-expansion-panel-header class="color-surface"> <mat-panel-title> @@ -125,13 +174,8 @@ </mat-expansion-panel-header> <div *ngFor="let question of keyword.questions"> <mat-divider></mat-divider> - <app-topic-dialog-comment - [question]="question" - [keyword]="keyword.keyword" - [maxShowedCharachters]="140" - [isCollapsed]="!panelOpenState" - [profanityFilter]="profanityFilter" - ></app-topic-dialog-comment> + <app-topic-dialog-comment [question]="question" [keyword]="keyword.keyword" [maxShowedCharachters]="140" + [isCollapsed]="!panelOpenState" [profanityFilter]="profanityFilter"></app-topic-dialog-comment> </div> <!-- Only visible when not editing --> @@ -142,7 +186,7 @@ <mat-icon class="primary">edit</mat-icon> </button> <button class="margin-right" mat-icon-button style="align-self:flex-end;" - (click)="openConfirmDialog(keyword)"> + (click)="openConfirmDialog('delete-message','delete',keyword)"> <mat-icon class="red">delete</mat-icon> </button> </div> @@ -150,17 +194,18 @@ <!-- Only visible when editing --> <div *ngIf="edit"> <mat-divider></mat-divider> - <mat-form-field> <mat-label>{{'topic-cloud-dialog.edit-keyword-tip' | translate}}</mat-label> <input matInput id="{{'edit-input'+i}}" [(ngModel)]="newKeyword"> </mat-form-field> <!-- TODO: textinput and buttons in one row --> <div align="end"> - <button mat-raised-button class="redBackground margin-right" (click)="cancelEdit()">{{'topic-cloud-dialog.cancel' | translate}}</button> - <button mat-raised-button class="primaryBackground" (click)="confirmEdit(keyword)">{{'topic-cloud-dialog.save' | translate}}</button> + <button mat-raised-button class="redBackground margin-right" + (click)="cancelEdit()">{{'topic-cloud-dialog.cancel' | translate}}</button> + <button mat-raised-button class="primaryBackground" (click)="confirmEdit(keyword)">{{'topic-cloud-dialog.save' + | translate}}</button> </div> </div> - </mat-expansion-panel><br> + </mat-expansion-panel> </mat-accordion> </mat-dialog-content> 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 427087987b7078fa7811a59dda8bb3061625c7b0..f44532d248f95cfcc58ff91faad7bb06a4d2e4e3 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 @@ -8,6 +8,14 @@ margin-right: 16px; } +.margin-left { + margin-left: 8px; +} + +.margin-bottom { + margin-bottom: 16px; +} + .primary { color: var(--primary); background: none; @@ -33,6 +41,11 @@ mat-icon { fill: currentColor; } +mat-list-item:hover { + background-color: rgba(128, 128, 128, 0.2); + +} + .color-surface { background-color: var(--surface); } @@ -41,7 +54,11 @@ mat-icon { background-color: var(--dialog); } -.search{ +.color-on-surface { + color: var(--on-surface); +} + +.search { box-sizing: border-box; padding: 0 10px 0 5px; border: none; @@ -82,17 +99,38 @@ mat-panel-title, mat-panel-description { opacity: 1.0 !important; } -.new-Badword .mat-expansion-panel-header-title, -.new-Badword .mat-expansion-panel-header-description { +.new-profanity-word .mat-expansion-panel-header-title, +.new-profanity-word .mat-expansion-panel-header-description { flex-basis: 0; } -.new-Badword .mat-expansion-panel-header-description, -.new-Badword .button { +.new-profanity-word .mat-expansion-panel-header-description, +.new-profanity-word .button { justify-content: space-between; align-items: center; } -.new-Badword .mat-form-field + .mat-form-field { +.new-profanity-word .mat-form-field + .mat-form-field { margin-left: 8px; } + +.vertical-center { + margin-top: auto; + margin-bottom: auto; +} + +.radio-button-item{ + margin-bottom: 7px; + color: var(--on-surface); +} + +.radio-button-group{ + display: flex; + flex-direction: column; + margin: 15px 0; +} + +mat-dialog-content { + max-height: 80vh!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 8b306ff956eaf8ffc24badb18dca45529e5126c8..b5b55d9ca573918ab7ab771be1fb8778b82eb7d7 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,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TagCloudComponent } from '../../tag-cloud/tag-cloud.component'; import { NotificationService } from '../../../../services/util/notification.service'; @@ -7,33 +7,37 @@ import { AuthenticationService } from '../../../../services/http/authentication. import { UserRole } from '../../../../models/user-roles.enum'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../../services/util/language.service'; -import { FormControl } from '@angular/forms'; -import { SpacyService } from '../../../../services/http/spacy.service'; import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; +import { TopicCloudAdminData } from './TopicCloudAdminData'; +import { KeywordOrFulltext } from './TopicCloudAdminData'; @Component({ selector: 'app-topic-cloud-administration', templateUrl: './topic-cloud-administration.component.html', styleUrls: ['./topic-cloud-administration.component.scss'] }) -export class TopicCloudAdministrationComponent implements OnInit { +export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { public panelOpenState = false; - public considerVotes: boolean; // should be sent back to tagCloud component - public profanityFilter = true; // should be sent back to tagCloud component - public hideIrrelevant: boolean; // should be sent back to tagCloud component + public considerVotes: boolean; + public profanityFilter: boolean; + public blacklistIsActive: boolean; + keywordOrFulltextENUM = KeywordOrFulltext; newKeyword = undefined; edit = false; isCreatorOrMod: boolean; - enterBadword = false; - enterIrrelevantWord = false; - newBadWord: string = undefined; - newIrrelevantWord: string = undefined; - + enterProfanityWord = false; + enterBlacklistWord = false; + newProfanityWord: string = undefined; + newBlacklistWord: string = undefined; sortMode = 'alphabetic'; searchedKeyword = undefined; searchMode = false; filteredKeywords: Keyword[] = []; - + showProfanityList = false; + showBlacklistWordList = false; + showSettingsPanel = false; + keywordORfulltext: string = undefined; + userRole: UserRole; keywords: Keyword[] = [ { keywordID: 1, @@ -86,37 +90,73 @@ export class TopicCloudAdministrationComponent implements OnInit { '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' ] }, - ]; + private topicCloudAdminData: TopicCloudAdminData; constructor(public cloudDialogRef: MatDialogRef<TagCloudComponent>, - public confirmDialog: MatDialog, - private notificationService: NotificationService, - private authenticationService: AuthenticationService, - private translateService: TranslateService, - private langService: LanguageService, - private topicCloudAdminService: TopicCloudAdminService) { - - this.langService.langEmitter.subscribe(lang => { - this.translateService.use(lang); - }); - } + public confirmDialog: MatDialog, + private notificationService: NotificationService, + private authenticationService: AuthenticationService, + private translateService: TranslateService, + private langService: LanguageService, + private topicCloudAdminService: TopicCloudAdminService) { + this.langService.langEmitter.subscribe(lang => { + this.translateService.use(lang); + }); + } ngOnInit(): void { this.translateService.use(localStorage.getItem('currentLang')); this.checkIfUserIsModOrCreator(); this.checkIfThereAreQuestions(); this.sortQuestions(); + this.setDefaultAdminData(); + } + + ngOnDestroy(){ + this.setAdminData(); + } + + setAdminData(){ + this.topicCloudAdminData = { + blacklist: this.topicCloudAdminService.getBlacklistWords(this.profanityFilter, this.blacklistIsActive), + considerVotes: this.considerVotes, + profanityFilter: this.profanityFilter, + blacklistIsActive: this.blacklistIsActive, + keywordORfulltext: KeywordOrFulltext[this.keywordORfulltext] + }; + this.topicCloudAdminService.setAdminData(this.topicCloudAdminData); + } + + setDefaultAdminData() { + this.topicCloudAdminData = this.topicCloudAdminService.getAdminData; + if (this.topicCloudAdminData) { + this.considerVotes = this.topicCloudAdminData.considerVotes; + this.profanityFilter = this.topicCloudAdminData.profanityFilter; + this.blacklistIsActive = this.topicCloudAdminData.blacklistIsActive; + this.keywordORfulltext = KeywordOrFulltext[this.topicCloudAdminData.keywordORfulltext]; + } } getKeywordWithoutProfanity(keyword: string): string { return this.topicCloudAdminService.filterProfanityWords(keyword); } + getProfanityList() { + return this.topicCloudAdminService.getProfanityList(); + } + + getBlacklist() { + return this.topicCloudAdminService.getBlacklist(); + } + sortQuestions(sortMode?: string) { if (sortMode !== undefined) { this.sortMode = sortMode; @@ -153,7 +193,6 @@ export class TopicCloudAdministrationComponent implements OnInit { editKeyword(index: number): void { this.edit = true; - setTimeout(() => { document.getElementById('edit-input'+ index).focus(); }, 0); @@ -180,12 +219,17 @@ export class TopicCloudAdministrationComponent implements OnInit { } confirmEdit(key: Keyword): void { - this.keywords.map(keyword => { + for (const keyword of this.keywords){ if (keyword.keywordID === key.keywordID) { - this.integrateIfKeywordExists(keyword, this.newKeyword.trim().toLowerCase()); + const key2 = this.checkIfKeywordExists(this.newKeyword.trim().toLowerCase()); + if (key2){ + this.openConfirmDialog('merge-message', 'merge', keyword, key2); + } else { keyword.keyword = this.newKeyword.trim(); + } + break; } - }); + } this.edit = false; this.newKeyword = undefined; this.sortQuestions(); @@ -194,14 +238,17 @@ export class TopicCloudAdministrationComponent implements OnInit { } } - openConfirmDialog(keyword: Keyword): void { + openConfirmDialog(msg: string, _confirmLabel: string, keyword: Keyword, mergeTarget?: Keyword) { + const translationPart = 'topic-cloud-confirm-dialog.'+msg; const confirmDialogRef = this.confirmDialog.open(TopicCloudConfirmDialogComponent, { - data: {topic: keyword.keyword} + data: {topic: keyword.keyword, message: translationPart, confirmLabel: _confirmLabel} }); confirmDialogRef.afterClosed().subscribe(result => { if (result === 'delete') { this.deleteKeyword(keyword); + } else if (result === 'merge') { + this.mergeKeywords(keyword, mergeTarget); } }); } @@ -217,15 +264,12 @@ export class TopicCloudAdministrationComponent implements OnInit { } } - //TODO: confirm dialog -> keyword does already exist, do you want to merge the questions with the existing keyword? - - integrateIfKeywordExists(keyword: Keyword, keyname: string) { - const key = this.checkIfKeywordExists(keyname); - if (key !== undefined){ - key.questions.map(question => { - keyword.questions.push(question); + mergeKeywords(key1: Keyword, key2: Keyword) { + if (key1 !== undefined && key2 !== undefined){ + key1.questions.map(question => { + key2.questions.push(question); }); - this.deleteKeyword(key); + this.deleteKeyword(key1); } } @@ -238,34 +282,36 @@ export class TopicCloudAdministrationComponent implements OnInit { return undefined; } - focusBadWordInput() { + focusInput(id: string) { setTimeout(() => { - document.getElementById('bad-word-input').focus(); + document.getElementById(id).focus(); }, 100); } - focusIrrelevantWordInput() { - setTimeout(() => { - document.getElementById('irrelevant-word-input').focus(); - }, 100); - } - - addBadword() { - this.topicCloudAdminService.addToBadwordList(this.newBadWord); - this.newBadWord = undefined; + addProfanityWord() { + this.topicCloudAdminService.addToProfanityList(this.newProfanityWord); + this.newProfanityWord = undefined; if (this.searchMode){ this.searchKeyword(); } } - addIrrelevantWord() { - this.topicCloudAdminService.addToIrrelevantwordList(this.newIrrelevantWord); - this.newIrrelevantWord = undefined; + addBlacklistWord() { + this.topicCloudAdminService.addToBlacklistWordList(this.newBlacklistWord); + this.newBlacklistWord = undefined; if (this.searchMode){ this.searchKeyword(); } } + removeWordFromProfanityList(word: string) { + this.topicCloudAdminService.removeFromProfanityList(word); + } + + removeWordFromBlacklist(word: string) { + this.topicCloudAdminService.removeWordFromBlacklist(word); + } + refreshAllLists(){ this.searchKeyword(); } diff --git a/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.html b/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.html index a6ec0ecf8685871eac234e9b4739bad642ae53eb..d7a9aa208eb87920f9920b61adff9e965be8c33b 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.html +++ b/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.html @@ -1,18 +1,10 @@ <h2 mat-dialog-title>{{'topic-cloud-confirm-dialog.confirm' | translate}}</h2> <mat-divider></mat-divider> -<p>{{'topic-cloud-confirm-dialog.will-be-deleted' | translate }} '{{data.topic}}'.</p> - -<!-- <div mat-dialog-actions align="end"> - <button class="primary" mat-raised-button [mat-dialog-close]="false"> - {{'topic-cloud-confirm-dialog.cancel' | translate}}</button> - <button class="warn" mat-raised-button [mat-dialog-close]="true"> - {{'topic-cloud-confirm-dialog.delete' | translate}} - </button> -</div> --> +<p>{{ data.message | translate}} '{{data.topic}}'.</p> <app-dialog-action-buttons buttonsLabelSection="topic-cloud-confirm-dialog" - confirmButtonLabel="delete" + [confirmButtonLabel]= data.confirmLabel [confirmButtonType]=confirmButtonType [cancelButtonClickAction]="buildCloseDialogActionCallback()" [confirmButtonClickAction]="buildDeleteAccountActionCallback()" diff --git a/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.ts b/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.ts index 6e76bd59c5372940dba3585d3d52e558cae2d159..54b69f1c9b18a8dfd2fa4c38d27d10dee3120067 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component.ts @@ -1,7 +1,6 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { DialogConfirmActionButtonType } from '../../dialog/dialog-action-buttons/dialog-action-buttons.component'; -import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-topic-cloud-confirm-dialog', @@ -11,10 +10,9 @@ import { TranslateService } from '@ngx-translate/core'; export class TopicCloudConfirmDialogComponent implements OnInit { confirmButtonType: DialogConfirmActionButtonType = DialogConfirmActionButtonType.Alert; - + confirmLabel = this.data.confirmLabel; constructor( public confirmDialogRef: MatDialogRef<TopicCloudConfirmDialogComponent>, - private translationService: TranslateService, @Inject(MAT_DIALOG_DATA) public data: DialogData) { } ngOnInit(): void { @@ -39,10 +37,12 @@ export class TopicCloudConfirmDialogComponent implements OnInit { * Returns a lambda which executes the dialog dedicated action on call. */ buildDeleteAccountActionCallback(): () => void { - return () => this.close('delete'); + return () => this.close(this.data.confirmLabel); } } export interface DialogData { topic: string; + message: string; + confirmLabel: string; } diff --git a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts index 51f47e4974425653da4ff91849644578622467aa..1fd01557fbe4c4e1e12024680b5cf91a4e7787a7 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.ts @@ -85,8 +85,8 @@ export class TopicCloudFilterComponent implements OnInit { if (c.userNumber) { userSet.add(c.userNumber); } - if (c.keywords) { - c.keywords.forEach(k => { + if (c.keywordsFromQuestioner) { + c.keywordsFromQuestioner.forEach(k => { keywordSet.add(k); }); } 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 47ced959bb8c89ec18f8cdb825f59157f3b49bf6..a346dd2a1784d11facede53cc5973348a954592b 100644 --- a/src/app/components/shared/comment-list/comment-list.component.html +++ b/src/app/components/shared/comment-list/comment-list.component.html @@ -3,7 +3,7 @@ [ngClass]="{'search-container' : !scroll, 'search-container-fixed' : scroll}" (window:scroll)="checkScroll()" fxLayoutAlign="center"> - + <button id="filter-close-button" mat-icon-button class="searchBarButton" @@ -239,7 +239,7 @@ mat-icon-button aria-labelledby="add" class="fab_add_comment" - (click)="openCreateDialog()" + (click)="this.createCommentWrapper.openCreateDialog(this.user)" matTooltip="{{ 'comment-list.add-comment' | translate }}"> <mat-icon>add</mat-icon> </button> 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 24dd7916d4b157200cacdf367f44199b810d462e..2e25c141dbb9a8e7b2d708368cc82f26b369c115 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -32,6 +32,7 @@ import { ModeratorService } from '../../../services/http/moderator.service'; import { TopicCloudFilterComponent } from '../_dialogs/topic-cloud-filter/topic-cloud-filter.component'; import { CommentFilterOptions } from '../../../utils/filter-options'; import { isObjectBindingPattern } from 'typescript'; +import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper'; export enum Period { FROMNOW = 'from-now', @@ -104,6 +105,7 @@ export class CommentListComponent implements OnInit, OnDestroy { fromNow: number; moderatorIds: string[]; commentsEnabled: boolean; + createCommentWrapper: CreateCommentWrapper = null; constructor( private commentService: CommentService, @@ -130,13 +132,16 @@ export class CommentListComponent implements OnInit, OnDestroy { initNavigation() { const navigation = {}; const nav = (b, c) => navigation[b] = c; - nav('createQuestion', () => this.openCreateDialog()); + nav('createQuestion', () => this.createCommentWrapper.openCreateDialog(this.user)); nav('moderator', () => { const dialogRef = this.dialog.open(ModeratorsComponent, { width: '400px', }); dialogRef.componentInstance.roomId = this.room.id; }); + this.eventService.on<string>('setTagConfig').subscribe(tag => { + this.clickedOnTag(tag); + }); nav('tags', () => { const updRoom = JSON.parse(JSON.stringify(this.room)); const dialogRef = this.dialog.open(TagsComponent, { @@ -224,6 +229,8 @@ export class CommentListComponent implements OnInit, OnDestroy { this.moderationEnabled = this.room.moderated; this.directSend = this.room.directSend; this.commentsEnabled = (this.userRole > 0) || !this.room.closed; + this.createCommentWrapper = new CreateCommentWrapper(this.translateService, + this.notificationService, this.commentService, this.dialog, this.room); localStorage.setItem('moderationEnabled', JSON.stringify(this.moderationEnabled)); if (!this.authenticationService.hasAccess(this.shortId, UserRole.PARTICIPANT)) { this.roomService.addToHistory(this.room.id); @@ -235,6 +242,7 @@ export class CommentListComponent implements OnInit, OnDestroy { .subscribe(comments => { this.comments = comments; this.getComments(); + this.eventService.broadcast('commentListCreated', null); }); /** if (this.userRole === UserRole.PARTICIPANT) { @@ -256,8 +264,8 @@ export class CommentListComponent implements OnInit, OnDestroy { this.getCurrentFilter().writeFilter(); } - private getCurrentFilter() : CommentFilterOptions { - let filter = new CommentFilterOptions(); + private getCurrentFilter(): CommentFilterOptions { + const filter = new CommentFilterOptions(); filter.filterSelected = this.currentFilter; filter.paused = this.freeze; filter.periodSet = this.period; @@ -400,7 +408,7 @@ export class CommentListComponent implements OnInit, OnDestroy { case this.ack: const isNowAck = <boolean>value; if (!isNowAck) { - this.comments = this.comments.filter(function (el) { + this.comments = this.comments.filter((el) => { return el.id !== payload.id; }); this.setTimePeriod(); @@ -427,7 +435,7 @@ export class CommentListComponent implements OnInit, OnDestroy { break; case 'CommentDeleted': for (let i = 0; i < this.comments.length; i++) { - this.comments = this.comments.filter(function (el) { + this.comments = this.comments.filter((el) => { return el.id !== payload.id; }); } @@ -441,55 +449,6 @@ export class CommentListComponent implements OnInit, OnDestroy { closeDialog() { this.dialog.closeAll(); } - openCreateDialog(): void { - const dialogRef = this.dialog.open(CreateCommentComponent, { - width: '900px', - maxWidth: 'calc( 100% - 50px )', - maxHeight: 'calc( 100vh - 50px )', - autoFocus: false, - }); - dialogRef.componentInstance.user = this.user; - dialogRef.componentInstance.roomId = this.roomId; - let tags; - tags = []; - if (this.room.tags) { - tags = this.room.tags; - } - dialogRef.componentInstance.tags = tags; - dialogRef.afterClosed() - .subscribe(result => { - if (result) { - this.send(result); - } else { - return; - } - }); - } - - - send(comment: Comment): void { - let message; - if (this.directSend) { - this.translateService.get('comment-list.comment-sent').subscribe(msg => { - message = msg; - }); - comment.ack = true; - } else { - if (this.userRole === 1 || this.userRole === 2 || this.userRole === 3) { - this.translateService.get('comment-list.comment-sent').subscribe(msg => { - message = msg; - }); - comment.ack = true; - } - if (this.userRole === 0) { - this.translateService.get('comment-list.comment-sent-to-moderator').subscribe(msg => { - message = msg; - }); - } - } - this.commentService.addComment(comment).subscribe(); - this.notificationService.show(message); - } filterComments(type: string, compare?: any): void { this.currentFilter = type; @@ -522,7 +481,7 @@ export class CommentListComponent implements OnInit, OnDestroy { return c.userNumber === compare; case this.keyword: this.selectedKeyword = compare; - return c.keywords != null ? c.keywords.includes(compare) : false; + return c.keywordsFromQuestioner != null ? c.keywordsFromQuestioner.includes(compare) : false; case this.answer: return c.answer; case this.unanswered: @@ -548,13 +507,12 @@ export class CommentListComponent implements OnInit, OnDestroy { } 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), dateB = new Date(b.timestamp); + 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) => { - return this.isCreatedByModeratorOrCreator(a) ? -1 : this.isCreatedByModeratorOrCreator(b) ? 1 : 0; - }); + return sortedArray.sort((a, b) => this.isCreatedByModeratorOrCreator(a) ? -1 : this.isCreatedByModeratorOrCreator(b) ? 1 : 0); } isCreatedByModeratorOrCreator(comment: Comment): boolean { @@ -605,8 +563,8 @@ export class CommentListComponent implements OnInit, OnDestroy { this.translateService.get('comment-list.comment-stream-started').subscribe(msg => { this.notificationService.show(msg); }); - - let filter = this.getCurrentFilter(); + + const filter = this.getCurrentFilter(); filter.writeFilter(); } @@ -683,7 +641,7 @@ export class CommentListComponent implements OnInit, OnDestroy { } this.getCurrentFilter().writeFilter(); - + this.filterComments(this.currentFilter); this.titleService.attachTitle('(' + this.commentsFilteredByTime.length + ')'); } diff --git a/src/app/components/shared/tag-cloud/demoData.ts b/src/app/components/shared/tag-cloud/demoData.ts deleted file mode 100644 index 9992f77908eaadf4767829ea50274ecafef2a0c5..0000000000000000000000000000000000000000 --- a/src/app/components/shared/tag-cloud/demoData.ts +++ /dev/null @@ -1,57 +0,0 @@ -export { demoMap }; -const demoMap = new Map<string, number>(); - demoMap.set("Topic 1",1); - demoMap.set("Topic 2 of 1",1); - demoMap.set("Topic 3 of 1",1); - demoMap.set("Topic 4 of 1",1); - demoMap.set("Topic 5 of 1",1); - demoMap.set("Topic 6 of 1",1); - demoMap.set("Topic 7 of 1",1); - demoMap.set("Topic 8 of 1",1); - demoMap.set("Topic 9 of 1",1); - demoMap.set("Topic 10 of 1",1); - demoMap.set("Topic 2",2); - demoMap.set("Topic 2 of 2",2); - demoMap.set("Topic 3 of 2",2); - demoMap.set("Topic 4 of 2",2); - demoMap.set("Topic 5 of 2",2); - demoMap.set("Topic 6 of 2",2); - demoMap.set("Topic 7 of 2",2); - demoMap.set("Topic 8 of 2",2); - demoMap.set("Topic 9 of 2",2); - demoMap.set("Topic 3",3); - demoMap.set("Topic 2 of 3",3); - demoMap.set("Topic 3 of 3",3); - demoMap.set("Topic 4 of 3",3); - demoMap.set("Topic 5 of 3",3); - demoMap.set("Topic 6 of 3",3); - demoMap.set("Topic 7 of 3",3); - demoMap.set("Topic 8 of 3",3); - demoMap.set("Topic 4",4); - demoMap.set("Topic 2 of 4",4); - demoMap.set("Topic 3 of 4",4); - demoMap.set("Topic 4 of 4",4); - demoMap.set("Topic 5 of 4",4); - demoMap.set("Topic 6 of 4",4); - demoMap.set("Topic 7 of 4",4); - demoMap.set("Topic 5",5); - demoMap.set("Topic 2 of 5",5); - demoMap.set("Topic 3 of 5",5); - demoMap.set("Topic 4 of 5",5); - demoMap.set("Topic 5 of 5",5); - demoMap.set("Topic 6 of 5",5); - demoMap.set("Topic 6",6); - demoMap.set("Topic 2 of 6",6); - demoMap.set("Topic 3 of 6",6); - demoMap.set("Topic 4 of 6",6); - demoMap.set("Topic 5 of 6",6); - demoMap.set("Topic 7",7); - demoMap.set("Topic 2 of 7",7); - demoMap.set("Topic 3 of 7",7); - demoMap.set("Topic 4 of 7",7); - demoMap.set("Topic 8",8); - demoMap.set("Topic 2 of 8",8); - demoMap.set("Topic 3 of 8",8); - demoMap.set("Topic 9",9); - demoMap.set("Topic 2 of 9",9); - demoMap.set("Topic 10",10); \ No newline at end of file diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.html b/src/app/components/shared/tag-cloud/tag-cloud.component.html index 16503d3c23d80a55096b785b95eb4c42cbaa7e14..509971aa8bae17c92eb112e1b9e6ad0cf07c01fe 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html @@ -12,6 +12,7 @@ class="spacyTagCloud" (window:resize)="onResize($event)" (afterInit)="initTagCloud()" + (clicked)="openTags($event)" [data]="data" [width]="options.width" [height]="options.height" diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.ts b/src/app/components/shared/tag-cloud/tag-cloud.component.ts index f4a61776121d31497188f1b0dfcbdd6df9b8f2e4..74565ecbcdfea3530e546eb9ffd9743ec8139562 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts @@ -8,18 +8,15 @@ import { ZoomOnHoverOptions } from 'angular-tag-cloud-module'; import { CommentService } from '../../../services/http/comment.service'; -import { SpacyService } from '../../../services/http/spacy.service'; -import { Comment } from '../../../models/comment'; import { LanguageService } from '../../../services/util/language.service'; import { TranslateService } from '@ngx-translate/core'; -import { CreateCommentComponent } from '../_dialogs/create-comment/create-comment.component'; import { MatDialog } from '@angular/material/dialog'; import { User } from '../../../models/user'; import { Room } from '../../../models/room'; import { NotificationService } from '../../../services/util/notification.service'; import { EventService } from '../../../services/util/event.service'; import { AuthenticationService } from '../../../services/http/authentication.service'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { UserRole } from '../../../models/user-roles.enum'; import { RoomService } from '../../../services/http/room.service'; import { ThemeService } from '../../../../theme/theme.service'; @@ -27,6 +24,7 @@ import { cloneParameters, CloudParameters, CloudTextStyle, CloudWeightSettings } import { TopicCloudAdministrationComponent } from '../_dialogs/topic-cloud-administration/topic-cloud-administration.component'; import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service'; import { TagCloudDataManager } from './tag-cloud.data-manager'; +import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper'; class CustomPosition implements Position { left: number; @@ -117,7 +115,7 @@ const getDefaultCloudParameters = (): CloudParameters => { delayWord: 0, randomAngles: false, checkSpelling: true, - sortAlphabetically: true, + sortAlphabetically: false, textTransform: CloudTextStyle.lowercase, cloudWeightSettings: weightSettings }; @@ -161,9 +159,10 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { themeSubscription = null; readonly dataManager: TagCloudDataManager; private _currentSettings: CloudParameters; + private _createCommentWrapper: CreateCommentWrapper = null; + private _subscriptionCommentlist = null; constructor(private commentService: CommentService, - private spacyService: SpacyService, private langService: LanguageService, private translateService: TranslateService, public dialog: MatDialog, @@ -173,7 +172,8 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { private route: ActivatedRoute, protected roomService: RoomService, private themeService: ThemeService, - private wsCommentService: WsCommentServiceService) { + private wsCommentService: WsCommentServiceService, + private router: Router) { this.roomId = localStorage.getItem('roomId'); this.langService.langEmitter.subscribe(lang => { this.translateService.use(lang); @@ -200,13 +200,14 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { this.updateGlobalStyles(); this.headerInterface = this.eventService.on<string>('navigate').subscribe(e => { if (e === 'createQuestion') { - this.openCreateDialog(); + this._createCommentWrapper.openCreateDialog(this.user); } else if (e === 'topicCloudConfig') { this.configurationOpen = !this.configurationOpen; this.dataManager.demoActive = !this.dataManager.demoActive; } else if (e === 'topicCloudAdministration') { this.dialog.open(TopicCloudAdministrationComponent, { - minWidth: '50%' + minWidth: '50%', + maxHeight: '80%' }); } }); @@ -229,6 +230,8 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { this.room = room; this.roomId = room.id; this.directSend = this.room.directSend; + this._createCommentWrapper = new CreateCommentWrapper(this.translateService, + this.notificationService, this.commentService, this.dialog, this.room); if (!this.authenticationService.hasAccess(this.shortId, UserRole.PARTICIPANT)) { this.roomService.addToHistory(this.room.id); this.authenticationService.setAccess(this.shortId, UserRole.PARTICIPANT); @@ -318,7 +321,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { if (remaining > 0) { --countFiler[tagData.adjustedWeight]; } - let rotation = this._currentSettings.cloudWeightSettings[tagData.adjustedWeight].rotation; + let rotation = Math.random() < 0.5 ? this._currentSettings.cloudWeightSettings[tagData.adjustedWeight].rotation : 0; if (rotation === null || this._currentSettings.randomAngles) { rotation = Math.floor(Math.random() * 30 - 15); } @@ -369,51 +372,16 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { } } - openCreateDialog(): void { - const dialogRef = this.dialog.open(CreateCommentComponent, { - width: '900px', - maxWidth: 'calc( 100% - 50px )', - maxHeight: 'calc( 100vh - 50px )', - autoFocus: false, - }); - dialogRef.componentInstance.user = this.user; - dialogRef.componentInstance.roomId = this.roomId; - let tags; - tags = []; - if (this.room.tags) { - tags = this.room.tags; - } - dialogRef.componentInstance.tags = tags; - dialogRef.afterClosed() - .subscribe(result => { - if (result) { - this.send(result); - } else { - return; - } - }); - } - - send(comment: Comment): void { - if (this.directSend) { - this.translateService.get('comment-list.comment-sent').subscribe(msg => { - this.notificationService.show(msg); - }); - comment.ack = true; - } else { - if (this.userRole === 1 || this.userRole === 2 || this.userRole === 3) { - this.translateService.get('comment-list.comment-sent').subscribe(msg => { - this.notificationService.show(msg); - }); - comment.ack = true; - } - if (this.userRole === 0) { - this.translateService.get('comment-list.comment-sent-to-moderator').subscribe(msg => { - this.notificationService.show(msg); - }); - } + openTags(tag: CloudData): void { + if(this._subscriptionCommentlist !== null){ + return; } - this.commentService.addComment(comment).subscribe(); + this._subscriptionCommentlist = this.eventService.on('commentListCreated').subscribe(() => { + //send tag.text instead of 'Autos' -> wait for group 3 to implement... + this.eventService.broadcast('setTagConfig', tag.text); + this._subscriptionCommentlist.unsubscribe(); + }); + this.router.navigate(['../'], {relativeTo: this.route}); } private redraw(): void { @@ -506,5 +474,4 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy { } return 0; } - } diff --git a/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts b/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts index 79ebcc754954ed89cf5588130404db354de193e0..2c870737338f2f0263899efac73ffdd491369db3 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts @@ -95,6 +95,7 @@ export class TagCloudDataManager { } this._roomId = roomId; this.onUpdateData(); + //TODO Optimize for special events => better performance this._wsCommentSubscription = this._wsCommentService .getCommentStream(this._roomId).subscribe(e => this.onUpdateData()); } @@ -205,7 +206,6 @@ export class TagCloudDataManager { return; } let newData: TagCloudData; - //TODO SORT if (this._isAlphabeticallySorted) { newData = new Map<string, TagCloudDataTagEntry>([...current] .sort(([aTag], [bTag]) => aTag.localeCompare(bTag))); @@ -213,7 +213,6 @@ export class TagCloudDataManager { newData = new Map<string, TagCloudDataTagEntry>([...current] .sort(([_, aTagData], [__, bTagData]) => bTagData.weight - aTagData.weight)); } - //TODO APPLY OTHER this._dataBus.next(newData); } @@ -252,10 +251,19 @@ export class TagCloudDataManager { const data: TagCloudData = new Map<string, TagCloudDataTagEntry>(); const users = new Set<number>(); for (const comment of this._lastFetchedComments) { - //TODO Check supply types - let keywords = comment.keywords || []; + let keywords = comment.keywordsFromQuestioner; + if (this._supplyType === TagCloudDataSupplyType.keywordsAndFullText) { + if (!keywords || !keywords.length) { + keywords = comment.keywordsFromSpacy; + } + } else if (this._supplyType === TagCloudDataSupplyType.fullText) { + keywords = comment.keywordsFromSpacy; + } + if (!keywords) { + keywords = []; + } for (const keyword of keywords) { - //TODO Check spelling + //TODO Check spelling & check profanity let current = data.get(keyword); if (current === undefined) { current = {cachedVoteCount: 0, comments: [], weight: 0, adjustedWeight: 0}; diff --git a/src/app/models/comment.ts b/src/app/models/comment.ts index 45a55c60dcc1bd33798e2b196bce73dd3fb680ce..2c00741a2d8fcf731699596822cea5716a524ebc 100644 --- a/src/app/models/comment.ts +++ b/src/app/models/comment.ts @@ -20,8 +20,8 @@ export class Comment { answer: string; userNumber: number; number: number; - keywordsFromSpacy: string[]; keywordsFromQuestioner: string[]; + keywordsFromSpacy: string[]; constructor(roomId: string = '', creatorId: string = '', @@ -38,8 +38,8 @@ export class Comment { tag: string = '', answer: string = '', userNumber: number = 0, - keywordsFromSpacy: string[] = [], - keywordsFromQuestioner: string[] = []) { + keywordsFromQuestioner: string[] = [], + keywordsFromSpacy: string[] = []) { this.id = ''; this.roomId = roomId; this.creatorId = creatorId; @@ -57,7 +57,7 @@ export class Comment { this.tag = tag; this.answer = answer; this.userNumber = userNumber; - this.keywordsFromSpacy = keywordsFromSpacy; this.keywordsFromQuestioner = keywordsFromQuestioner; + this.keywordsFromSpacy = keywordsFromSpacy; } } diff --git a/src/app/services/http/comment.service.ts b/src/app/services/http/comment.service.ts index 0bc95c2c13c38c2831992d463dff7303e3df07bc..a282312f83c9ea33d4fd7dda7c28a0db004a71f4 100644 --- a/src/app/services/http/comment.service.ts +++ b/src/app/services/http/comment.service.ts @@ -84,7 +84,8 @@ export class CommentService extends BaseHttpService { { roomId: comment.roomId, body: comment.body, read: comment.read, creationTimestamp: comment.timestamp, tag: comment.tag, - keywordsFromSpacy: JSON.stringify(comment.keywordsFromSpacy), keywordsFromQuestioner: JSON.stringify(comment.keywordsFromQuestioner) + keywordsFromSpacy: JSON.stringify(comment.keywordsFromSpacy), + keywordsFromQuestioner: JSON.stringify(comment.keywordsFromQuestioner) }, httpOptions).pipe( tap(_ => ''), catchError(this.handleError<Comment>('addComment')) @@ -109,14 +110,16 @@ export class CommentService extends BaseHttpService { properties: { roomId: roomId, ack: true }, externalFilters: {} }, httpOptions).pipe( - map(commentList => { - return commentList.map(comment => { + map(commentList => commentList.map(comment => { const newComment = this.parseUserNumber(comment); - // @ts-ignore - newComment.keywords = JSON.parse(newComment.keywords as string); + newComment.keywordsFromQuestioner = + // @ts-ignore + newComment.keywordsFromQuestioner ? JSON.parse(newComment.keywordsFromQuestioner as string) : null; + newComment.keywordsFromSpacy = + // @ts-ignore + newComment.keywordsFromSpacy ? JSON.parse(newComment.keywordsFromSpacy as string) : null; return newComment; - }); - }), + })), tap(_ => ''), catchError(this.handleError<Comment[]>('getComments', [])) ); diff --git a/src/app/services/http/spacy.service.ts b/src/app/services/http/spacy.service.ts index 7c39e05509362f0f1d577506e6810223332ec248..92825bb8dc2bb79c17bd494feaedab11be444a4a 100644 --- a/src/app/services/http/spacy.service.ts +++ b/src/app/services/http/spacy.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { BaseHttpService } from './base-http.service'; -import { catchError } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; export type Model = 'de' | 'en' | 'fr'; @@ -73,6 +73,12 @@ export class SpacyService extends BaseHttpService { super(); } + getKeywords(text: string, model: string): Observable<string[]> { + return this.analyse(text, model).pipe( + map(result => result.words.filter(v => v.tag.charAt(0) === 'N').map(v => v.text)) + ); + } + analyse(text: string, model: string): Observable<Result> { const url = '/spacy'; return this.http.post<Result>(url, {text, model}, httpOptions) diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index ac2fac3ec162ca126be09c33ceaec301014091cf..401b26c6e01d5f684f0b305f03fbcd0abddda2d3 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -1,64 +1,126 @@ +import { stringify } from '@angular/compiler/src/util'; import { Injectable } from '@angular/core'; import * as BadWords from 'naughty-words'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { BaseHttpService } from '../http/base-http.service'; -import { catchError } from 'rxjs/operators'; - -const httpOptions = { - headers: new HttpHeaders({ 'Content-Type': 'application/json' }) -}; - +// eslint-disable-next-line max-len +import { TopicCloudAdminData, KeywordOrFulltext } from '../../../app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) -export class TopicCloudAdminService extends BaseHttpService{ - +export class TopicCloudAdminService { private badWords = []; - private irrelevantWords = []; + private profanityWords = []; + private blacklist = []; // should be stored in backend + private profanityKey = 'custom-Profanity-List'; - constructor(private http: HttpClient) { - super(); + constructor() { this.badWords = BadWords; - this.badWords['custom'] = []; + /* 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; + } + + get getAdminData(): TopicCloudAdminData { + let data = JSON.parse(localStorage.getItem('Topic-Cloud-Admin-Data')); + if (!data) { + data = { + blacklist: this.profanityWords, + considerVotes: false, + profanityFilter: true, + blacklistIsActive: false, + keywordORfulltext: KeywordOrFulltext.keyword + }; + } + return data; } - get getBadWordList(): string[]{ - return this.badWords['custom']; + setAdminData(adminData: TopicCloudAdminData){ + localStorage.setItem('Topic-Cloud-Admin-Data', JSON.stringify(adminData)); } filterProfanityWords(str: string): string { let questionWithProfanity = str; - // TODO: another languages - /* put all arrays of languages together */ - const profanityWords = this.badWords['en'].concat(this.badWords['de']) - .concat(this.badWords['fr']).concat(this.badWords['custom']); - profanityWords.map(word =>{ - questionWithProfanity = questionWithProfanity.toLowerCase().includes(word.toLowerCase()) ? - this.replaceString(questionWithProfanity.toLowerCase(), word.toLowerCase(), this.generateXWord(word.length)) - : questionWithProfanity; + this.profanityWords.concat(this.getProfanityList()).map((word) => { + questionWithProfanity = questionWithProfanity + .toLowerCase() + .includes(word.toLowerCase()) + ? this.replaceString( + questionWithProfanity.toLowerCase(), + word.toLowerCase(), + this.generateCensoredWord(word.length) + ) + : questionWithProfanity; }); return questionWithProfanity; } - addToBadwordList(word: string) { + getProfanityList(): string[] { + const list = localStorage.getItem(this.profanityKey); + return list ? list.split(',') : []; + } + + addToProfanityList(word: string) { if (word !== undefined) { - this.badWords['custom'].push(word); + const newList = this.getProfanityList(); + if (newList.includes(word)){ + return; + } + newList.push(word); + localStorage.setItem(this.profanityKey, newList.toString()); } } - addToIrrelevantwordList(word: string) { + removeFromProfanityList(profanityWord: string) { + const list = this.getProfanityList(); + list.map(word => { + if (word === profanityWord){ + list.splice(list.indexOf(word, 0), 1); + } + }); + localStorage.setItem(this.profanityKey, list.toString()); + } + + removeProfanityList(){ + localStorage.removeItem(this.profanityKey); + } + + getBlacklist(): string[] { + return this.blacklist; + } + + addToBlacklistWordList(word: string) { if (word !== undefined) { - this.irrelevantWords.push(word); + this.blacklist.push(word); } } - private replaceString(str: string, search: string, replace: string){ + removeWordFromBlacklist(word: string) { + this.blacklist.splice(this.blacklist.indexOf(word), 1); + } + + + private replaceString(str: string, search: string, replace: string) { return str.split(search).join(replace); } - private generateXWord(count: number){ + private generateCensoredWord(count: number) { let res = ''; - for (let i = 0; i < count; i++){ + for (let i = 0; i < count; i++) { res += '*'; } return res; diff --git a/src/app/utils/CreateCommentWrapper.ts b/src/app/utils/CreateCommentWrapper.ts new file mode 100644 index 0000000000000000000000000000000000000000..db05b6f9774847eab36484b4dfdf40a0f9b97ad5 --- /dev/null +++ b/src/app/utils/CreateCommentWrapper.ts @@ -0,0 +1,65 @@ +import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; + +import { CreateCommentComponent } from '../components/shared/_dialogs/create-comment/create-comment.component'; +import { User } from '../models/user'; +import { Room } from '../models/room'; +import { Comment } from '../models/comment'; +import { NotificationService } from '../services/util/notification.service'; +import { UserRole } from '../models/user-roles.enum'; +import { CommentService } from '../services/http/comment.service'; + +export class CreateCommentWrapper { + constructor(private translateService: TranslateService, + private notificationService: NotificationService, + private commentService: CommentService, + private dialog: MatDialog, + private room: Room) { + } + + openCreateDialog(user: User): void { + const dialogRef = this.dialog.open(CreateCommentComponent, { + width: '900px', + maxWidth: 'calc( 100% - 50px )', + maxHeight: 'calc( 100vh - 50px )', + autoFocus: false, + }); + dialogRef.componentInstance.user = user; + dialogRef.componentInstance.roomId = this.room.id; + dialogRef.componentInstance.tags = this.room.tags || []; + dialogRef.afterClosed() + .subscribe(result => { + if (result) { + this.send(result, user.role); + } else { + return; + } + }); + } + + send(comment: Comment, userRole: UserRole): void { + if (this.room.directSend) { + this.translateService.get('comment-list.comment-sent').subscribe(msg => { + this.notificationService.show(msg); + }); + comment.ack = true; + } else { + switch (userRole) { + case UserRole.EDITING_MODERATOR: + case UserRole.EXECUTIVE_MODERATOR: + case UserRole.CREATOR: + this.translateService.get('comment-list.comment-sent').subscribe(msg => { + this.notificationService.show(msg); + }); + comment.ack = true; + break; + case UserRole.PARTICIPANT: + this.translateService.get('comment-list.comment-sent-to-moderator').subscribe(msg => { + this.notificationService.show(msg); + }); + break; + } + } + this.commentService.addComment(comment).subscribe(); + } +} diff --git a/src/app/utils/filter-comments.ts b/src/app/utils/filter-comments.ts index 103a381b434b7c912ff266ff2d9e5433efdcd0a9..6486f9b3537fbbcfa714b27214dfc7a3cc122114 100644 --- a/src/app/utils/filter-comments.ts +++ b/src/app/utils/filter-comments.ts @@ -71,7 +71,7 @@ export class CommentFilterUtils { } if (filter.keywordSelected != '') { - return com.keywords.includes(filter.keywordSelected); + return com.keywordsFromQuestioner.includes(filter.keywordSelected); } if (filter.tagSelected != ''){ return com.tag === filter.tagSelected; diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 7b203c28d2853d8f421af4fc9787e3477dca7810..38e15dac7460deeeeeb0223520ef9774bed14f5b 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -246,25 +246,32 @@ "no-keywords-note": "Es gibt keine Themen", "consider-votes": "Votes berücksichtigen", "profanity": "Schimpfwörter zensieren", - "hide-irrelevant": "Irrelevante Stichworte verbergen", + "hide-blacklist-words": "Blacklist Stichworte verbergen", "sort-alpha": "Alphabetisch", "sort-count": "Fragenanzahl", "sort-vote": "Votes", "keyword-search": "Stichwort suchen", - "add-profanity-word": "Wort zur Zensurliste hinzufügen", - "add-irrelevant-word": "Wort zur Irrelevantliste hinzufügen", - "send": "Abschicken", + "edit-profanity-list": "Zensurliste bearbeiten", + "edit-blacklist-list": "Blackliste bearbeiten", + "add-word": "Wort hinzufügen", "enter-word": "Wort eingeben", - "english": "Englisch", - "german": "Deutsch", - "french": "Französisch", - "sendKey": "schickt keyword zu spacy" + "settings": "Einstellungen", + "keyword": "Stichwort", + "full-text": "Voller Text", + "both": "Beide", + "select-choice": "Wählen Sie aus", + "show-blacklist": "Zeige Blackliste", + "hide-blacklist": "Verberge Blackliste", + "show-profanity-list": "Zeige Schimpfwortliste", + "hide-profanity-list": "Verberge Schimpfwortliste" }, "topic-cloud-confirm-dialog": { "cancel": "Abbrechen", "delete": "Löschen", - "confirm": "Sind Sie sicher?", - "will-be-deleted": "Thema wird gelöscht" + "merge": "Zusammenfügen", + "delete-message": "Möchten Sie das Thema wirklich löschen", + "merge-message": "Stichwort existiert schon, möchten Sie die beiden Stichworte zusammenfügen", + "confirm": "Sind Sie sicher?" }, "dialog-comment": { "read-more": "Mehr lesen", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 4ba45a661059979ba167213f93265cddb233d1ad..4e481147a7f80aa5276cdb9fba94fd701642b0c0 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -251,25 +251,32 @@ "no-keywords-note": "There are no topics!", "consider-votes": "Consider Votes", "profanity": "Censor profanity", - "hide-irrelevant": "Hide irrelevant keywords", + "hide-blacklist-words": "Hide blacklist keywords", "sort-alpha": "Alphabetically", "sort-count": "Questions count", "sort-vote": "Votes", "keyword-search": "Search keyword", - "add-profanity-word": "Add word to profanity list", - "add-irrelevant-word": "Add word to irrelevant list", - "send": "Send", + "edit-profanity-list": "Edit profanity list", + "edit-blacklist-list": "Edit blacklist list", + "add-word": "Add Word", "enter-word": "Enter word", - "english": "English", - "german": "German", - "french": "French", - "sendKey": "send keyword to spacy" + "settings": "Settings", + "keyword": "Keyword", + "full-text": "Full-text", + "both": "Both", + "select-choice": "Select one", + "show-blacklist": "Show blacklist", + "hide-blacklist": "Hide blacklist", + "show-profanity-list": "Show profanity list", + "hide-profanity-list": "Hide profanity list" }, "topic-cloud-confirm-dialog":{ "cancel": "Cancel", "delete": "Delete", - "confirm": "Are you sure?", - "will-be-deleted": "Topic will be deleted" + "merge": "Merge", + "delete-message": "Do you really want to delete the topic", + "merge-message": "Keyword already exists, do you want to merge both keywords", + "confirm": "Are you sure?" }, "dialog-comment":{ "read-more": "Read more",