diff --git a/src/app/components/creator/_dialogs/room-edit/room-edit.component.html b/src/app/components/creator/_dialogs/room-edit/room-edit.component.html index 734041e4ef037c6bd716d4339057061f64f4852b..b79de81de353d5e43d26e68356f38d5a7ad9514b 100644 --- a/src/app/components/creator/_dialogs/room-edit/room-edit.component.html +++ b/src/app/components/creator/_dialogs/room-edit/room-edit.component.html @@ -56,12 +56,16 @@ </mat-tab> </mat-tab-group> <div fxLayoutAlign="center center"> + <label for="myCheck" style="background: darkgreen; font-size: larger;">{{ 'room-page.block' | translate }} </label> + <input type="checkbox" id= "myCheck" [(ngModel)]= "check" > + <button mat-raised-button class="delete" (click)="openDeleteRoomDialog()" aria-labelledby="delete-room"> <mat-icon>delete</mat-icon> {{ 'room-page.delete-room' | translate }}</button> + </div> </div> </div> @@ -69,6 +73,7 @@ <app-dialog-action-buttons buttonsLabelSection="room-page" confirmButtonLabel="update" + [cancelButtonClickAction]="buildCloseDialogActionCallback()" [confirmButtonClickAction]="buildSaveActionCallback()" ></app-dialog-action-buttons> diff --git a/src/app/components/creator/_dialogs/room-edit/room-edit.component.ts b/src/app/components/creator/_dialogs/room-edit/room-edit.component.ts index cd26cf219d0ac1c5e78777beafa4d27d574f6215..8a1222d4cc5cb81029e35432c29dccdc3214c451 100644 --- a/src/app/components/creator/_dialogs/room-edit/room-edit.component.ts +++ b/src/app/components/creator/_dialogs/room-edit/room-edit.component.ts @@ -11,6 +11,7 @@ import { RoomCreatorPageComponent } from '../../room-creator-page/room-creator-p import { EventService } from '../../../../services/util/event.service'; import { RoomDeleted } from '../../../../models/events/room-deleted'; + @Component({ selector: 'app-room-edit', templateUrl: './room-edit.component.html', @@ -18,6 +19,7 @@ import { RoomDeleted } from '../../../../models/events/room-deleted'; }) export class RoomEditComponent implements OnInit { editRoom: Room; + check: boolean = false; roomNameFormControl = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(30)]); @@ -68,6 +70,8 @@ export class RoomEditComponent implements OnInit { } save(): void { + this.editRoom.closed = this.check; + this.roomService.updateRoom(this.editRoom).subscribe(r => this.editRoom = r); if (!this.roomNameFormControl.hasError('required') && !this.roomNameFormControl.hasError('minlength') && !this.roomNameFormControl.hasError('maxlength')) { diff --git a/src/app/components/shared/_dialogs/create-comment/create-comment.component.html b/src/app/components/shared/_dialogs/create-comment/create-comment.component.html index d052cb60f18109b9315286557ca552be6fc712a8..eb4b8d88af2d0af963d0e30a5ac2b763dfb28580 100644 --- a/src/app/components/shared/_dialogs/create-comment/create-comment.component.html +++ b/src/app/components/shared/_dialogs/create-comment/create-comment.component.html @@ -1,7 +1,9 @@ <ars-row ars-flex-box> <ars-row> <div class="lang-selection"> - <button class="lang-btn" mat-button (click)="select.open()"> + <button class="lang-btn" mat-button (click)="select.open()" + matTooltip="{{ 'spacy-dialog.lang-button-hint' | translate }}" + matTooltipShowDelay="750"> <i class="material-icons">language</i> {{'spacy-dialog.' + (selectedLang === 'auto' ? 'auto' : languagetoolService.mapLanguageToSpacyModel(selectedLang)) | translate}} <mat-select class="select-list" #select [(ngModel)]="selectedLang"> @@ -40,12 +42,12 @@ <mat-divider></mat-divider> </ars-row> <ars-row [height]="12"></ars-row> - <ars-row [overflow]="'auto'" - style="max-height:calc( 100vh - 250px )"> + <ars-row [overflow]="'visible'" style="max-height:calc( 100vh - 250px )"> <mat-form-field style="width:100%;"> <input [disabled]="true" matInput> <div [contentEditable]="true" [spellcheck]="false" + spellcheck="false" (focus)="eventService.makeFocusOnInputTrue()" style="margin-top:15px;width:100%;" (blur)="eventService.makeFocusOnInputFalse()" @@ -65,7 +67,7 @@ </mat-hint> <mat-hint align="end"> <span aria-hidden="true"> - {{commentBody.innerText.length}} / {{user.role === 3 ? 1000 : 500}} + {{inputText.length}} / {{user.role === 3 ? 1000 : 500}} </span> </mat-hint> </mat-form-field> @@ -89,10 +91,10 @@ <mat-divider></mat-divider> </ars-row> <ars-row ars-flex-box> - <ars-fill> - </ars-fill> <ars-col> - <button (click)="grammarCheck(commentBody)">{{ 'comment-page.grammar-check' | translate}}</button> + <button class="mat-flat-button spell-button" (click)="grammarCheck(commentBody)">{{ 'comment-page.grammar-check' | translate}}</button> + </ars-col> + <ars-col> <app-dialog-action-buttons buttonsLabelSection="comment-page" confirmButtonLabel="send" diff --git a/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss b/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss index 29c6198bbf2586f39398bbbcc98b05c642293855..41d5b82dbc9498fee7f9f9c119a673d3708c554a 100644 --- a/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss +++ b/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss @@ -26,6 +26,11 @@ app-comment-list { outline: none; } } +.spell-button{ + background-color: var(--primary); + color: var(--on-primary); + margin-top: 1rem; +} .send { color: var(--on-primary); diff --git a/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts b/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts index 801608786cba8f82a276509e1a557aeb0d73b933..cb679c21af613760a779abe073e0d26a80fa9718 100644 --- a/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts +++ b/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Comment } from '../../../../models/comment'; import { NotificationService } from '../../../../services/util/notification.service'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -15,7 +15,7 @@ import { LanguagetoolService, Language } from '../../../../services/http/languag templateUrl: './create-comment.component.html', styleUrls: ['./create-comment.component.scss'] }) -export class CreateCommentComponent implements OnInit { +export class CreateCommentComponent implements OnInit, OnDestroy { comment: Comment; @@ -23,6 +23,7 @@ export class CreateCommentComponent implements OnInit { roomId: string; tags: string[]; selectedTag: string; + inputText = ''; body: string; languages: Language[] = ['de-DE', 'en-US', 'fr', 'auto']; @@ -30,7 +31,7 @@ export class CreateCommentComponent implements OnInit { bodyForm = new FormControl('', [Validators.required]); - @ViewChild('commentBody', { static: true })commentBody: HTMLDivElement; + @ViewChild('commentBody', { static: true }) commentBody: HTMLDivElement; constructor( private notification: NotificationService, @@ -47,9 +48,24 @@ export class CreateCommentComponent implements OnInit { this.translateService.use(localStorage.getItem('currentLang')); setTimeout(() => { document.getElementById('answer-input').focus(); + document.addEventListener('click', this.onDocumentClick); }, 0); } + onDocumentClick(e) { + const container = document.getElementsByClassName('dropdownBlock'); + Array.prototype.forEach.call(container, (elem) => { + if (!elem.contains(e.target) && (!(e.target as Node).parentElement.classList.contains('markUp') + || (e.target as HTMLElement).dataset.id !== ((elem as Node).parentElement as HTMLElement).dataset.id)) { + (elem as HTMLElement).style.display = 'none'; + } + }); + } + + ngOnDestroy() { + document.removeEventListener('click', this.onDocumentClick); + } + onNoClick(): void { this.dialogRef.close(); } @@ -78,27 +94,36 @@ export class CreateCommentComponent implements OnInit { } openSpacyDialog(comment: Comment): void { - this.checkSpellings(comment.body).subscribe((res) => { - let commentBodyChecked = comment.body; - const commentLang = this.languagetoolService.mapLanguageToSpacyModel(res.language.code); - for(let i = res.matches.length - 1; i >= 0; i--){ - commentBodyChecked = commentBodyChecked.substr(0, res.matches[i].offset) + - commentBodyChecked.substr(res.matches[i].offset + res.matches[i].length, commentBodyChecked.length); - } - const dialogRef = this.dialog.open(SpacyDialogComponent, { - data: { - comment, - commentLang, - commentBodyChecked + this.checkSpellings(this.inputText).subscribe((res) => { + const words: string[] = this.inputText.trim().split(' '); + const errorQuotient = (res.matches.length * 100) / words.length; + + if (errorQuotient <= 20) { + let commentBodyChecked = this.inputText; + const commentLang = this.languagetoolService.mapLanguageToSpacyModel(res.language.code); + + for (let i = res.matches.length - 1; i >= 0; i--) { + commentBodyChecked = commentBodyChecked.substr(0, res.matches[i].offset) + + commentBodyChecked.substr(res.matches[i].offset + res.matches[i].length, commentBodyChecked.length); } - }); - dialogRef.afterClosed() - .subscribe(result => { - if (result) { - this.dialogRef.close(result); + const dialogRef = this.dialog.open(SpacyDialogComponent, { + data: { + comment, + commentLang, + commentBodyChecked } }); + + dialogRef.afterClosed() + .subscribe(result => { + if (result) { + this.dialogRef.close(result); + } + }); + } else { + this.dialogRef.close(comment); + } }); }; @@ -123,41 +148,79 @@ export class CreateCommentComponent implements OnInit { } maxLength(commentBody: HTMLDivElement): void { - // Cut the text down to 500 or 1000 chars depending on the user role. - if(this.user.role === 3 && commentBody.innerText.length > 1000) { + this.inputText = commentBody.innerText; + if (this.user.role === 3 && commentBody.innerText.length > 1000) { commentBody.innerText = commentBody.innerText.slice(0, 1000); - } else if(this.user.role !== 3 && commentBody.innerText.length > 500){ + } else if (this.user.role !== 3 && commentBody.innerText.length > 500) { commentBody.innerText = commentBody.innerText.slice(0, 500); } this.body = commentBody.innerText; } grammarCheck(commentBody: HTMLDivElement): void { - let wrongWords: string[] = []; + const wrongWords: string[] = []; + commentBody.innerHTML = this.inputText; this.checkSpellings(commentBody.innerText).subscribe((wordsCheck) => { - if(wordsCheck.matches.length > 0 ) { + if (wordsCheck.matches.length > 0) { wordsCheck.matches.forEach(grammarError => { const wrongWord = commentBody.innerText.slice(grammarError.offset, grammarError.offset + grammarError.length); wrongWords.push(wrongWord); }); + this.checkSpellings(commentBody.innerHTML).subscribe((res) => { - for(let i = res.matches.length - 1; i >= 0; i--){ // Reverse for loop to make sure the offset is right. + for (let i = res.matches.length - 1; i >= 0; i--) { const wrongWord = commentBody.innerHTML .slice(res.matches[i].offset, res.matches[i].offset + res.matches[i].length); - if (wrongWords.includes(wrongWord)) { // Only replace the real Words, excluding the HTML tags - const msg = res.matches[i].message; // The explanation of the suggestion for improvement - const suggestions = res.matches[i].replacements; // The suggestions for improvement. Access: suggestions[x].value - const replacement = '<span style="text-decoration: underline wavy red">' // set the Styling for all marked words - // Select menu with suggestions has to be injected here. - + wrongWord + - '</span>'; + if (wrongWords.includes(wrongWord)) { + const suggestions: any[] = res.matches[i].replacements; + let displayOptions = 3; + let suggestionsHTML = ''; + + if (!suggestions.length) { + suggestionsHTML = '<span style="color: black; display: block; text-align: center;">' + res.matches[i].message + '</span>'; + } + + if (suggestions.length < displayOptions) { + displayOptions = suggestions.length; + } + + for (let j = 0; j < displayOptions; j++) { + // eslint-disable-next-line max-len + suggestionsHTML += '<span class="suggestions"' + ' style="color: black; display: block; text-align: center; cursor: pointer;">' + suggestions[j].value + '</span>'; + } + + const replacement = + '<div class="markUp" data-id="'+i+'" style="position: relative; display: inline-block; border-bottom: 1px dotted black">' + + ' <span data-id="' + i + '" style="text-decoration: underline wavy red; cursor: pointer;">' + + wrongWord + + ' </span>' + + // eslint-disable-next-line max-len + ' <div class="dropdownBlock" style="display: none; width: 160px; background-color: white; border-style: solid; border-color: var(--primary); color: #fff; text-align: center; border-radius: 6px; padding: 5px 0; position: absolute; z-index: 1000; bottom: 100%; left: 50%; margin-left: -80px;">' + + suggestionsHTML + + ' </div>' + + '</div>'; + commentBody.innerHTML = commentBody.innerHTML.substr(0, res.matches[i].offset) + - replacement + - commentBody.innerHTML.substr(res.matches[i].offset + wrongWord.length, - commentBody.innerHTML.length); + replacement + commentBody.innerHTML.substr(res.matches[i].offset + wrongWord.length, commentBody.innerHTML.length); } } + + setTimeout(() => { + Array.from(document.getElementsByClassName('markUp')).forEach(marked => { + marked.addEventListener('click', () => { + ((marked as HTMLElement).lastChild as HTMLElement).style.display = 'block'; + setTimeout(() => { + Array.from(document.getElementsByClassName('suggestions')).forEach(e => { + e.addEventListener('click', () => { + e.parentElement.parentElement.outerHTML = e.innerHTML; + this.inputText = commentBody.innerText; + }); + }); + }, 500); + }); + }); + }, 500); }); } }); diff --git a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html index 6f7dfe97e96d9aaf5c3e8865b3132239acc01fd2..3a2cab8045ff60eeb87392acaf297dbc199f95ce 100644 --- a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html +++ b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html @@ -2,8 +2,19 @@ <div class="anchor-wrp"> <span *ngIf="keywords.length > 0"> <ars-row class="select-all-section"> - <mat-checkbox class="select-all-checkbox" id="checkAll" (change)="selectAll(checkall.checked)" #checkall></mat-checkbox> - <mat-label class="select-all-label" for="checkAll" (click)="checkall.checked = !checkall.checked; selectAll(checkall.checked)"> + <mat-checkbox class="select-all-checkbox" + id="checkAll" + (change)="selectAll(checkall.checked)" + #checkall + matTooltip="{{ 'spacy-dialog.select-all-hint' | translate }}" + matTooltipShowDelay="750"> + </mat-checkbox> + <mat-label class="select-all-label" + for="checkAll" + (click)="checkall.checked = !checkall.checked; + selectAll(checkall.checked)" + matTooltip="{{ 'spacy-dialog.select-all-hint' | translate }}" + matTooltipShowDelay="750"> <mat-icon class="select-all-icon">playlist_add_check</mat-icon> {{ 'spacy-dialog.select-all' | translate }} </mat-label> @@ -22,16 +33,22 @@ <div class="keywords-actions"> <mat-checkbox [checked]="keyword.completed" (change)="keyword.selected = $event.checked" - [(ngModel)]="keyword.completed"> + [(ngModel)]="keyword.completed" + matTooltip="{{ 'spacy-dialog.select-keyword-hint' | translate }}" + matTooltipShowDelay="750"> </mat-checkbox> <button *ngIf="!keyword.editing" (click)="onEdit(keyword)" mat-icon-button - [ngClass]="{'keywords-actions-selected': keyword.selected}"> + [ngClass]="{'keywords-actions-selected': keyword.selected}" + matTooltip="{{ 'spacy-dialog.edit-keyword-hint' | translate }}" + matTooltipShowDelay="750"> <mat-icon>edit</mat-icon> </button> <button *ngIf="keyword.editing" (click)="onEndEditing(keyword)" mat-icon-button - class = "edit-accept"> + class = "edit-accept" + matTooltip="{{ 'spacy-dialog.editing-done-hint' | translate }}" + matTooltipShowDelay="750"> <mat-icon>check</mat-icon> </button> </div> diff --git a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss index 0185073a2871ecd5831c192434264b6b38a4b5da..8fc238f37097e3c64d50f079b433f42f1e91bac7 100644 --- a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss +++ b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss @@ -3,7 +3,7 @@ height: 40px !important; } .keywords-list { - height: 343px; + max-height: 340px; flex-grow: 1; flex-shrink: 1; overflow: auto; 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 2d2755d43f564dded9abe3b2a066a3fe3f4c5030..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 @@ -24,6 +24,7 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { commentLang: Model; commentBodyChecked: string; keywords: Keyword[] = []; + keywordsOriginal: Keyword[] = []; constructor( protected langService: LanguageService, @@ -45,45 +46,59 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { /** * Returns a lambda which closes the dialog on call. */ - buildCloseDialogActionCallback(): () => void { + buildCloseDialogActionCallback(): () => void { return () => this.dialogRef.close(); } buildCreateCommentActionCallback() { return () => { - this.comment.keywords = this.keywords.filter(kw => kw.selected).map(kw => kw.word); + this.comment.keywordsFromQuestioner = this.keywords.filter(kw => kw.selected).map(kw => kw.word); + this.comment.keywordsFromSpacy = this.keywordsOriginal.map(kw => kw.word); this.dialogRef.close(this.comment); }; } 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'); + } else if (this.commentLang === 'en') { + regex = new RegExp('(?!he|she|it|for|with)[a-z]{2,}(-[a-z]{2,})*', 'gi'); + } else { + regex = new RegExp('(?!au|de|la|le|en|un)[A-ZÀ-Ÿ]{2,}', 'gi'); + } // 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 => { - for(const word of res.words) { - if (word.tag.charAt(0) === 'N') { - words.push({ - word: word.text, - 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.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.html b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html index fdf95e7f172a29c2b8cefd8726b737e60b8d8500..b01a4c381f7168427e04dec18bdec40dd125fca3 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html +++ b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.html @@ -4,13 +4,35 @@ <mat-divider></mat-divider> <mat-radio-group [(ngModel)]="continueFilter" aria-label="Select an option"> - <mat-radio-button value="continueWithAll">{{'content.continue-with-all-questions' | translate}}</mat-radio-button> - <mat-radio-button checked="true" value="continueWithCurr">{{'content.continue-with-current-questions' | translate}}</mat-radio-button> - <mat-radio-button value="continueWithAllFromNow">{{'content.continue-with-all-questions-from-now' | translate}}</mat-radio-button> + <mat-radio-button value="continueWithAll"> + <div class="elementRow"> + <div class="elementText"> + {{'content.continue-with-all-questions' | translate}} + </div> + <div class="elementIcons"> + <mat-icon [inline]="true">comment</mat-icon> {{allCommentsCount}} + <mat-icon [inline]="true">person</mat-icon> {{allCommentsUsers}} + <mat-icon svgIcon="comment_tag" class="comment_tag-icon"></mat-icon> {{allCommentsKeywords}} + </div> + </div> + </mat-radio-button> + <mat-radio-button checked="true" value="continueWithCurr"> + <div class="elementRow"> + <div class="elementText"> + {{'content.continue-with-current-questions' | translate}} + </div> + <div class="elementIcons"> + <mat-icon [inline]="true">comment</mat-icon> {{filteredCommentsCount}} + <mat-icon [inline]="true">person</mat-icon> {{filteredCommentsUsers}} + <mat-icon svgIcon="comment_tag" class="comment_tag-icon"></mat-icon> {{filteredCommentsKeywords}} + </div> + </div> + </mat-radio-button> + <mat-radio-button value="continueWithAllFromNow"> + {{'content.continue-with-all-questions-from-now' | translate}} + </mat-radio-button> </mat-radio-group> - - -<app-dialog-action-buttons + <app-dialog-action-buttons buttonsLabelSection="content" confirmButtonLabel="continue" buttonIcon="cloud" diff --git a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss index d0f639b53f2a3aaebcca5c9dd5c1ae7f22d83027..2a514ac55d792d2202bb1c70ac45847d2f594f61 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss +++ b/src/app/components/shared/_dialogs/topic-cloud-filter/topic-cloud-filter.component.scss @@ -1,12 +1,32 @@ -mat-radio-button{ +mat-radio-button { margin: 5px; - } - - mat-radio-group { - display: flex; + display: flex; flex-direction: column; margin: 15px 0; +} + +::ng-deep .mat-radio-label-content { + width: 100%; +} + +.elementRow { + display: flex; + flex-direction: row; + width: 100% !important; +} + +.elementText { + align-content: flex-start; + margin-right: 10px; +} + +.elementIcons { + margin-left: auto; +} + +.comment_tag-icon { + height: 16px !important; } \ No newline at end of file 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 34b87be61cc03c919b9ab8f035c032609701d78d..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 @@ -5,8 +5,18 @@ import { TranslateService } from '@ngx-translate/core'; import { RoomCreatorPageComponent } from '../../../creator/room-creator-page/room-creator-page.component'; import { LanguageService } from '../../../../services/util/language.service'; import { EventService } from '../../../../services/util/event.service'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Router } from '@angular/router'; import { CommentFilterOptions } from '../../../../utils/filter-options'; +import { CommentService } from '../../../../services/http/comment.service'; +import { RoomService } from '../../../../services/http/room.service'; +import { Comment } from '../../../../models/comment'; + + +class CommentsCount { + comments : number; + users: number; + keywords: number; +} @Component({ selector: 'app-topic-cloud-filter', @@ -16,19 +26,27 @@ import { CommentFilterOptions } from '../../../../utils/filter-options'; export class TopicCloudFilterComponent implements OnInit { @Input() filteredComments: any; @Input() commentsFilteredByTime: any; + @Input() shortId: string; continueFilter = 'continueWithCurr'; tmpFilter : CommentFilterOptions; - shortId: string; - + allCommentsCount : number; + allCommentsUsers : number; + allCommentsKeywords : number; + + filteredCommentsCount : number; + filteredCommentsUsers : number; + filteredCommentsKeywords : number; + constructor(public dialogRef: MatDialogRef<RoomCreatorPageComponent>, public dialog: MatDialog, public notificationService: NotificationService, public translationService: TranslateService, protected langService: LanguageService, - private route: ActivatedRoute, private router: Router, + protected roomService: RoomService, + private commentService: CommentService, @Inject(MAT_DIALOG_DATA) public data: any, public eventService: EventService) { langService.langEmitter.subscribe(lang => translationService.use(lang)); @@ -38,40 +56,75 @@ export class TopicCloudFilterComponent implements OnInit { this.translationService.use(localStorage.getItem('currentLang')); this.tmpFilter = CommentFilterOptions.readFilter(); localStorage.setItem("filtertmp", JSON.stringify(this.tmpFilter)); - this.route.params.subscribe(params => { - this.shortId = params['shortId']; + + this.roomService.getRoomByShortId(this.shortId).subscribe(room => { + this.commentService.getAckComments(room.id).subscribe(comments => { + const counts = this.getCommentCounts(comments); + this.allCommentsCount = counts.comments; + this.allCommentsUsers = counts.users; + this.allCommentsKeywords = counts.keywords; + }); + this.commentService.getFilteredComments(room.id).subscribe(comments => { + const counts = this.getCommentCounts(comments); + this.filteredCommentsCount = counts.comments; + this.filteredCommentsUsers = counts.users; + this.filteredCommentsKeywords = counts.keywords; + }); }); } closeDialog(): void { } + getCommentCounts(comments : Comment[]) : CommentsCount { + let counts = new CommentsCount(); + let userSet = new Set<number>(); + let keywordSet = new Set<string>(); + + comments.forEach(c => { + if (c.userNumber) { + userSet.add(c.userNumber); + } + if (c.keywordsFromQuestioner) { + c.keywordsFromQuestioner.forEach(k => { + keywordSet.add(k); + }); + } + }); + + counts.comments = comments.length; + counts.users = userSet.size; + counts.keywords = keywordSet.size; + return counts; + } cancelButtonActionCallback(): () => void { return () => this.dialogRef.close('abort'); } - confirmButtonActionCallback(): () => void { - let filter : CommentFilterOptions; - - switch (this.continueFilter) { - case 'continueWithAll': - filter = new CommentFilterOptions(); // all questions allowed - break; - - case 'continueWithAllFromNow': - filter = CommentFilterOptions.generateFilterNow(this.tmpFilter.filterSelected); - break; + confirmButtonActionCallback() { + return () => { + let filter : CommentFilterOptions; + + switch (this.continueFilter) { + case 'continueWithAll': + filter = new CommentFilterOptions(); // all questions allowed + break; - case 'continueWithCurr': - filter = JSON.parse(localStorage.getItem("filtertmp")) as CommentFilterOptions; - break; - - default: - return; + case 'continueWithAllFromNow': + filter = CommentFilterOptions.generateFilterNow(this.tmpFilter.filterSelected); + break; + + case 'continueWithCurr': + filter = JSON.parse(localStorage.getItem("filtertmp")) as CommentFilterOptions; + break; + + default: + return; + } + + CommentFilterOptions.writeFilterStatic(filter); + this.dialogRef.close(this.router.navigateByUrl('/participant/room/' + this.shortId + '/comments/tagcloud')); } - - CommentFilterOptions.writeFilterStatic(filter); - return () => this.dialogRef.close(this.router.navigateByUrl('/participant/room/' + this.shortId + '/comments/tagcloud')); } -} +} \ No newline at end of file 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 2a231bc25557b2d4ab86693122dc9ff9713d1740..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> @@ -253,7 +253,8 @@ [user]="user" [disabled]="!commentsEnabled" (clickedOnTag)="clickedOnTag($event)" - (clickedUserNumber)="clickedUserNumber($event)"> + (clickedUserNumber)="clickedUserNumber($event)" + (clickedOnKeyword)="clickedOnKeyword($event)"> </app-comment> </div> 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 df09d1a86e928654615bb716e6e5762770180744..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', @@ -78,7 +79,10 @@ export class CommentListComponent implements OnInit, OnDestroy { moderator = 'moderator'; lecturer = 'lecturer'; tag = 'tag'; + selectedTag = ''; userNumber = 'userNumber'; + keyword = 'keyword'; + selectedKeyword = ''; answer = 'answer'; unanswered = 'unanswered'; owner = 'owner'; @@ -101,6 +105,7 @@ export class CommentListComponent implements OnInit, OnDestroy { fromNow: number; moderatorIds: string[]; commentsEnabled: boolean; + createCommentWrapper: CreateCommentWrapper = null; constructor( private commentService: CommentService, @@ -127,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, { @@ -221,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); @@ -232,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) { @@ -253,11 +264,13 @@ 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; + filter.keywordSelected = this.selectedKeyword; + filter.tagSelected = this.selectedTag; if (filter.periodSet == Period.FROMNOW) { filter.timeStampNow = new Date().getTime(); @@ -395,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(); @@ -422,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; }); } @@ -436,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; @@ -495,6 +459,7 @@ export class CommentListComponent implements OnInit, OnDestroy { this.sortComments(this.currentSort); return; } + console.log(compare); this.filteredComments = this.commentsFilteredByTime.filter(c => { switch (type) { case this.correct: @@ -510,9 +475,13 @@ export class CommentListComponent implements OnInit, OnDestroy { case this.unread: return !c.read; case this.tag: + this.selectedTag = compare; return c.tag === compare; case this.userNumber: return c.userNumber === compare; + case this.keyword: + this.selectedKeyword = compare; + return c.keywordsFromQuestioner != null ? c.keywordsFromQuestioner.includes(compare) : false; case this.answer: return c.answer; case this.unanswered: @@ -538,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 { @@ -564,6 +532,10 @@ export class CommentListComponent implements OnInit, OnDestroy { this.filterComments(this.tag, tag); } + clickedOnKeyword(keyword: string): void { + this.filterComments(this.keyword, keyword); + } + clickedUserNumber(usrNumber: number): void { this.filterComments(this.userNumber, usrNumber); } @@ -575,7 +547,7 @@ export class CommentListComponent implements OnInit, OnDestroy { this.notificationService.show(msg); }); - let filter = CommentFilterOptions.generateFilterUntil(this.currentFilter, this.period, new Date().getTime()); + let filter = CommentFilterOptions.generateFilterUntil(this.currentFilter, this.period, new Date().getTime(), this.selectedTag, this.selectedKeyword); filter.writeFilter(); } @@ -591,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(); } @@ -669,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/comment/comment.component.html b/src/app/components/shared/comment/comment.component.html index d20bab4378abf1190895902501fa61bcc7700951..eb3945740c95b6bf9670cb879d4c0647dd38145e 100644 --- a/src/app/components/shared/comment/comment.component.html +++ b/src/app/components/shared/comment/comment.component.html @@ -256,10 +256,11 @@ <div fxLayout="column" fxLayoutAlign="center" - *ngIf="isStudent && !disabled" + *ngIf="isStudent" [ngClass]="{ '1': 'voteUp', '-1': 'voteDown', '0': 'reset'}[currentVote]"> <button mat-icon-button (click)="voteUp(comment)" + *ngIf="!disabled" matTooltip="{{ 'comment-page.vote-up' | translate }}" tabindex="0" attr.aria-labelledby="comment_vote_up{{ comment.id }}"> @@ -270,6 +271,7 @@ <span class="score">{{comment.score}}</span> <button mat-icon-button (click)="voteDown(comment)" + *ngIf="!disabled" matTooltip="{{ 'comment-page.vote-down' | translate }}" tabindex="0" attr.aria-labelledby="comment_vote_down{{ comment.id }}"> @@ -320,19 +322,19 @@ <div fxLayoutAlign="center center" matTooltip="{{ 'comment-page.keywords-per-question' | translate }}" [mat-menu-trigger-for]="keywordsMenu" - *ngIf="(comment.keywords != undefined && comment.keywords.length > 0)" + *ngIf="(comment.keywordsFromQuestioner != undefined && comment.keywordsFromQuestioner.length > 0)" class="comment-keywords"> <mat-icon svgIcon="comment_tag"></mat-icon> <span> - {{ 'comment-page.keywords' | translate }} + {{ this.selectedKeyword === '' ? ('comment-page.keywords' | translate ) : this.selectedKeyword}} </span> <mat-menu #keywordsMenu> <mat-list dense class="keywords-list"> - <mat-list-item *ngFor="let keyword of comment.keywords; let odd = odd; let even = even" + <mat-list-item *ngFor="let keyword of comment.keywordsFromQuestioner; let odd = odd; let even = even" [class.keywords-alternate]="odd" - [class.keywords-even]="even"> - <span class="keyword-span">{{keyword}}</span> + [class.keywords-even]="even"> + <span (click)="this.clickedOnKeyword.emit(keyword)" class="keyword-span">{{keyword}}</span> </mat-list-item> </mat-list> </mat-menu> diff --git a/src/app/components/shared/comment/comment.component.scss b/src/app/components/shared/comment/comment.component.scss index 8e8497d6e501367832cf9501af9e2f6e33ec711b..921bc0a869dd690eb03022a9e30c7d856143d180 100644 --- a/src/app/components/shared/comment/comment.component.scss +++ b/src/app/components/shared/comment/comment.component.scss @@ -194,6 +194,7 @@ mat-card-content > :first-child { } .keywords-even { background-color: var(--alt-surface); + cursor: pointer; } .keyword-span { color: var(--on-surface) !important; diff --git a/src/app/components/shared/comment/comment.component.ts b/src/app/components/shared/comment/comment.component.ts index 6943f71cf754333dfac1d13ff49efc176e50fe98..9e316cf823c1665625ed416eb3a20b7cd41cf157 100644 --- a/src/app/components/shared/comment/comment.component.ts +++ b/src/app/components/shared/comment/comment.component.ts @@ -42,6 +42,7 @@ export class CommentComponent implements OnInit, AfterViewInit { @Input() user: User; @Input() disabled = false; @Output() clickedOnTag = new EventEmitter<string>(); + @Output() clickedOnKeyword = new EventEmitter<string>(); @Output() clickedUserNumber = new EventEmitter<number>(); isStudent = false; isCreator = false; @@ -58,6 +59,7 @@ export class CommentComponent implements OnInit, AfterViewInit { @ViewChild('commentExpander', { static: true })commentExpander: RowComponent; isExpanded = false; isExpandable = false; + selectedKeyword: string = ''; constructor(protected authenticationService: AuthenticationService, private route: ActivatedRoute, diff --git a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html index fe6843e70a778497898e8deb431813613841fa7e..06bf0d6f8f0ce4f2a5efc5b9c067d68cdb94e280 100644 --- a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html +++ b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html @@ -21,6 +21,7 @@ attr.aria-labelledby="{{ ariaPrefix + 'cancel' | translate }}" (click)="performCancelButtonClickAction()" >{{ buttonsLabelSection + '.cancel' | translate}}</button> + </div> </div> <!--Hidden Div's for a11y-Descriptions--> diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index 802ce6f8bdf2fdb0ec48640ff655e8fb440d71a5..aabc5d3cea8c901a47d013f5a39e27b728d2673c 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -17,6 +17,10 @@ </h2> + <ng-container *ngIf="room && room.closed && !router.url.includes('/room-list/')"> + <mat-icon>block</mat-icon><h1>{{'header.questions-blocked'|translate}}</h1> + </ng-container> + <ars-style-btn-material [ngClass]="{'noOfQuestions': deviceType === 'desktop'}" *ngIf="router.url.includes('tagcloud')" @@ -121,7 +125,7 @@ <ng-container *ngIf="router.url.endsWith('/comments')"> <button mat-menu-item - *ngIf="user && !router.url.endsWith('moderator/comments') && ((user.role > 0) || ((user.role == 0) && room))" + *ngIf="user && !router.url.endsWith('moderator/comments') && ((user.role > 0) || ((user.role == 0) && room && !room.closed))" tabindex="0" (click)="navigateCreateQuestion();"> <mat-icon> @@ -347,6 +351,15 @@ <span>{{'header.delete-account' | translate}}</span> </button> + <ng-container *ngIf="router.url.includes('/creator') || router.url.includes('/moderator')"> + <button mat-menu-item + (click)="blockQuestions()" + [ngClass]="{'color-warn': room && room.closed}" + tabindex="0"> + <mat-icon class="color-warn">block</mat-icon> + <span>{{'header.block' | translate}}</span> + </button> + </ng-container> <button mat-menu-item (click)="logout()" diff --git a/src/app/components/shared/header/header.component.scss b/src/app/components/shared/header/header.component.scss index c36df0f777a8529ab3e3e983094459b11295425b..a6e4125f8ccaefefcaa5a7f435a7632a5452948b 100644 --- a/src/app/components/shared/header/header.component.scss +++ b/src/app/components/shared/header/header.component.scss @@ -120,4 +120,4 @@ svg { } h1{ color: red; -} \ No newline at end of file +} diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts index c756da05480e64471a63ac955802e8fb18edf25e..65d387666d2b9ac11a2935dfbf73971d1b319a73 100644 --- a/src/app/components/shared/header/header.component.ts +++ b/src/app/components/shared/header/header.component.ts @@ -116,14 +116,14 @@ export class HeaderComponent implements OnInit { if (val instanceof NavigationEnd) { /* segments gets all parts of the url */ const segments = this.router.parseUrl(this.router.url).root.children.primary.segments; + this.shortId = ''; + this.room = null; + if (segments && segments.length > 2) { if (!segments[2].path.includes('%')) { this.shortId = segments[2].path; localStorage.setItem('shortId', this.shortId); this.roomService.getRoomByShortId(this.shortId).subscribe(room => this.room = room); - } else { - this.shortId = ''; - this.room = null; } } } @@ -305,6 +305,7 @@ export class HeaderComponent implements OnInit { const confirmDialogRef = this.confirmDialog.open(TopicCloudFilterComponent, { autoFocus: false }); + confirmDialogRef.componentInstance.shortId = this.shortId; } public navigateTopicCloudConfig() { diff --git a/src/app/components/shared/questionwall/question-wall/question-wall.component.html b/src/app/components/shared/questionwall/question-wall/question-wall.component.html index cae420d7988319960459a8bc665ea6d3da7a765b..42e2c51fcc27f36c4daf9965b41d62eb5589da0a 100644 --- a/src/app/components/shared/questionwall/question-wall/question-wall.component.html +++ b/src/app/components/shared/questionwall/question-wall/question-wall.component.html @@ -6,6 +6,10 @@ <button ars-btn (click)="leave()" matRipple aria-labelledby="back-lbl"><i>arrow_back</i></button> </ars-col> </ars-fill> + <ng-container *ngIf="room && room.closed && !router.url.includes('/room-list/')"> + <mat-icon>block</mat-icon> + <h2>{{'question-wall.questions-blocked'|translate}}</h2> + </ng-container> <ars-col> <!-- centered col --> </ars-col> diff --git a/src/app/components/shared/questionwall/question-wall/question-wall.component.scss b/src/app/components/shared/questionwall/question-wall/question-wall.component.scss index 61c7abdadfd35de1f6accdad80415db75d6f93ec..b7718ca9d488e8824481dd726c4d6606f8570d02 100644 --- a/src/app/components/shared/questionwall/question-wall/question-wall.component.scss +++ b/src/app/components/shared/questionwall/question-wall/question-wall.component.scss @@ -303,3 +303,6 @@ .selection:focus{ background-color: black !important; } +h2{ + color: red; +} diff --git a/src/app/components/shared/room-list/room-list.component.html b/src/app/components/shared/room-list/room-list.component.html index b7e63704a82237938ae88acfc2cb9bb36301f274..56ffe83f6637c6bd4a03b1a46b387e68ccb97f3c 100644 --- a/src/app/components/shared/room-list/room-list.component.html +++ b/src/app/components/shared/room-list/room-list.component.html @@ -33,7 +33,6 @@ <td mat-cell class="clickable" *matCellDef="let room" (click)="setCurrentRoom(room.shortId)" routerLink="/{{ roleToString((room.role)) }}/room/{{ room.shortId }}"> <span matBadge="{{room.commentCount > 0 ? room.commentCount : null}}" matBadgePosition="before" matBadgeSize="small" matBadgeOverlap="false"> - <mat-icon *ngIf="room.closed" class="warn" [inline]="true">block</mat-icon> »{{room.name}}« </span> </td> @@ -47,6 +46,7 @@ <td mat-cell class="clickable" *matCellDef="let room" (click)="setCurrentRoom(room.shortId)" routerLink="/{{ roleToString((room.role)) }}/room/{{ room.shortId }}"> {{ room.shortId }} + <mat-icon *ngIf="room.closed" class="warn" [inline]="true">block</mat-icon> </td> </ng-container> 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 1d488d077c1638391e1b98accac44441fe1eb5c0..2c00741a2d8fcf731699596822cea5716a524ebc 100644 --- a/src/app/models/comment.ts +++ b/src/app/models/comment.ts @@ -20,7 +20,8 @@ export class Comment { answer: string; userNumber: number; number: number; - keywords: string[]; + keywordsFromQuestioner: string[]; + keywordsFromSpacy: string[]; constructor(roomId: string = '', creatorId: string = '', @@ -37,7 +38,8 @@ export class Comment { tag: string = '', answer: string = '', userNumber: number = 0, - keywords: string[] = []) { + keywordsFromQuestioner: string[] = [], + keywordsFromSpacy: string[] = []) { this.id = ''; this.roomId = roomId; this.creatorId = creatorId; @@ -55,6 +57,7 @@ export class Comment { this.tag = tag; this.answer = answer; this.userNumber = userNumber; - this.keywords = keywords; + 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 66ab1d529173dc3a6a5109e0bceba6970de095f3..a282312f83c9ea33d4fd7dda7c28a0db004a71f4 100644 --- a/src/app/services/http/comment.service.ts +++ b/src/app/services/http/comment.service.ts @@ -83,7 +83,9 @@ export class CommentService extends BaseHttpService { return this.http.post<Comment>(connectionUrl, { roomId: comment.roomId, body: comment.body, - read: comment.read, creationTimestamp: comment.timestamp, tag: comment.tag, keywords: JSON.stringify(comment.keywords) + read: comment.read, creationTimestamp: comment.timestamp, tag: comment.tag, + keywordsFromSpacy: JSON.stringify(comment.keywordsFromSpacy), + keywordsFromQuestioner: JSON.stringify(comment.keywordsFromQuestioner) }, httpOptions).pipe( tap(_ => ''), catchError(this.handleError<Comment>('addComment')) @@ -108,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 d23b880587dcc85657f9f289b7217a56ebe7dae6..6486f9b3537fbbcfa714b27214dfc7a3cc122114 100644 --- a/src/app/utils/filter-comments.ts +++ b/src/app/utils/filter-comments.ts @@ -70,6 +70,13 @@ export class CommentFilterUtils { } } + if (filter.keywordSelected != '') { + return com.keywordsFromQuestioner.includes(filter.keywordSelected); + } + if (filter.tagSelected != ''){ + return com.tag === filter.tagSelected; + } + return true; } diff --git a/src/app/utils/filter-options.ts b/src/app/utils/filter-options.ts index 0aae11a6db36e1cec60f307f021f76d62fc1c583..e00ed0f05ea143ef9ab39fb65c74e4aafeec6f73 100644 --- a/src/app/utils/filter-options.ts +++ b/src/app/utils/filter-options.ts @@ -14,6 +14,8 @@ export const enum FilterNames { export class CommentFilterOptions { filterSelected : string; + keywordSelected : string; + tagSelected : string; paused: boolean; timeStampUntil : number; @@ -23,6 +25,8 @@ export class CommentFilterOptions { constructor() { this.filterSelected = ''; + this.keywordSelected = ''; + this.tagSelected = ''; this.paused = false; this.periodSet = Period.ALL; } @@ -46,19 +50,25 @@ export class CommentFilterOptions { filter.filterSelected = filterSelected; filter.paused = false; + filter.tagSelected = ''; + filter.keywordSelected = ''; + filter.periodSet = Period.FROMNOW; filter.timeStampNow = new Date().getTime(); return filter; } - public static generateFilterUntil(filterSelected : string, periodSelected : Period, untilTime : number) : CommentFilterOptions { + public static generateFilterUntil(filterSelected : string, periodSelected : Period, untilTime : number, tagSelected : string, keywordSelected : string) : CommentFilterOptions { let filter = new CommentFilterOptions(); filter.filterSelected = filterSelected; filter.paused = true; filter.timeStampUntil = untilTime; + + filter.tagSelected = tagSelected; + filter.keywordSelected = keywordSelected; filter.periodSet = periodSelected; diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index 9f1a1b3fc098f804116c2fa5a8380f9a051bf715..016745a91b9ba22f2066fe94c3408506696383a4 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -88,7 +88,12 @@ "en": "Englisch", "fr": "Französisch", "empty-nouns": "Keine Nomen enthalten", - "select-all": "Alles auswählen" + "select-all": "Alles auswählen", + "lang-button-hint": "Ausgewählte Sprache für die Rechtschreibprüfung", + "select-all-hint": "Alle Stichwörter auswählen", + "select-keyword-hint": "Dieses Stickwort auswählen", + "edit-keyword-hint": "Stichwort editieren", + "editing-done-hint": "Editierung abschliessen" }, "comment-page": { "a11y-comment_delete": "Löscht diese Frage", @@ -308,6 +313,7 @@ "threshold": "Schwellenwert für die Anzeige? ", "threshold-description": "Lege den Schwellenwert fest, ab dem eine negativ bewertete Frage angezeigt werden soll.", "update": "Speichern", + "block" : "Fragen sperren", "update-description": "Speichert die Änderungen", "question": "Frage", "favorite": "Favorit", diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index 091ab1128c1b0eff6f06731c824f5dc8ce25155d..eac853c0b7ac0af21a5113a466656c9c520752d9 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -81,7 +81,7 @@ "token-time": "Token timestamp", "no-comments": "There are no questions yet.", "export-comments": "Save questions", - "questions-blocked": "Questions are blocked!" + "questions-blocked": "No further questions!" }, "spacy-dialog": { "auto": "auto", @@ -89,7 +89,12 @@ "en": "English", "fr": "French", "empty-nouns": "No nouns included", - "select-all": "Select all" + "select-all": "Select all", + "lang-button-hint": "Selected language for spell check", + "select-all-hint": "Select all keywords", + "select-keyword-hint": "Select this keyword", + "edit-keyword-hint": "Edit keyword", + "editing-done-hint": "Finish editing" }, "comment-page": { "a11y-comment_delete": "Deletes this question", @@ -309,6 +314,7 @@ "threshold": "Threshold value for display? ", "threshold-description": "Set the threshold value above which a negatively rated question should be displayed.", "update": "Save", + "block" : "Block questions", "update-description": "Save changes", "question": "Question", "favorite": "Favorite", diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json index 4682909194e86d628dda18e773ffeffe01394b0f..af3295e5a159ffdcd041c04bee2281fcc2d146d4 100644 --- a/src/assets/i18n/home/en.json +++ b/src/assets/i18n/home/en.json @@ -89,7 +89,7 @@ "motd": "News", "tag-cloud-config": "Modify cloud view", "tag-cloud-administration": "Edit cloud topics", - "questions-blocked": "Questions are blocked!", + "questions-blocked": "No further questions!", "overview-question-tooltip": "Number of questions", "overview-questioners-tooltip": "Number of questioners", "overview-keywords-tooltip": "Number of Keywords" diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index ef2092afc37aaf861f6da2df42c4a782b97bca25..51a120d9647d17c54accf38ef9fe49fab7cf6490 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -93,7 +93,12 @@ "en": "Englisch", "fr": "Französisch", "empty-nouns": "Keine Nomen enthalten", - "select-all": "Alles auswählen" + "select-all": "Alles auswählen", + "lang-button-hint": "Ausgewählte Sprache für die Rechtschreibprüfung", + "select-all-hint": "Alle Stichwörter auswählen", + "select-keyword-hint": "Dieses Stickwort auswählen", + "edit-keyword-hint": "Stichwort editieren", + "editing-done-hint": "Editierung abschliessen" }, "comment-page": { "a11y-comment_input": "Gib deine Frage ein", @@ -222,7 +227,8 @@ "prev-comment-lbl": "Vorherige Frage", "next-comment-lbl": "Nächste Frage", "overview-question-tooltip": "Anzahl gestellter Fragen", - "overview-questioners-tooltip": "Anzahl Fragensteller*innen" + "overview-questioners-tooltip": "Anzahl Fragensteller*innen", + "questions-blocked": "Fragen sind deaktiviert!" }, "tag-cloud": { "config": "Wolkenansicht ändern", @@ -240,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 c4040423140e9217e1ad951eb39fbddfc9be5502..fa25835a30329ae789f727d6f337f903640ec582 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -91,7 +91,7 @@ "token-time": "Token timestamp", "no-comments": "There are no questions yet.", "export-comments": "Save questions", - "questions-blocked": "Questions are blocked" + "questions-blocked": "No further questions" }, "content": { "cancel": "Cancel", @@ -103,7 +103,12 @@ "en": "English", "fr": "French", "empty-nouns": "No nouns included", - "select-all": "Select all" + "select-all": "Select all", + "lang-button-hint": "Selected language for spell check", + "select-all-hint": "Select all keywords", + "select-keyword-hint": "Select this keyword", + "edit-keyword-hint": "Edit keyword", + "editing-done-hint": "Finish editing" }, "comment-page": { "a11y-comment_input": "Enter your question", @@ -227,7 +232,8 @@ "prev-comment-lbl": "Previous question", "next-comment-lbl": "Next question", "overview-question-tooltip": "Number of questions", - "overview-questioners-tooltip": "Number of questioners" + "overview-questioners-tooltip": "Number of questioners", + "questions-blocked": "No further questions!" }, "tag-cloud": { "config": "Modify cloud view", @@ -245,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",