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 479407610b062b8325e81f40290498c157238ec5..9ee31f0d57d6a590ae5d4fe5040bc76c271b6f9e 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 @@ -58,10 +58,16 @@ <div fxLayoutAlign="center center"> <mat-slide-toggle [(ngModel)]= "check">{{ 'room-page.block' | translate }}</mat-slide-toggle> </div> - <div fxLayoutAlign="center center"> - <mat-slide-toggle (change)="showMessage('words-will-be-overwritten')" [(ngModel)]= "profanityCheck"> + <div fxLayoutAlign="center center" fxLayout="column"> + <mat-slide-toggle (change)="showMessage('words-will-be-overwritten', $event.checked)" [(ngModel)]= "profanityCheck"> {{ 'room-page.profanity-filter' | translate }} </mat-slide-toggle> + <mat-slide-toggle *ngIf="profanityCheck" (change)="showMessage('only-specific-language-will-be-filtered', $event.checked)" [(ngModel)]= "censorLanguageSpecificCheck"> + {{ 'room-page.language-specific-filter' | translate }} + </mat-slide-toggle> + <mat-slide-toggle *ngIf="profanityCheck" (change)="showMessage('partial-words-will-be-filtered', $event.checked)" [(ngModel)]= "censorPartialWordsCheck"> + {{ 'room-page.partial-words-filter' | translate }} + </mat-slide-toggle> </div> <div fxLayoutAlign="center center"> <!-- <input type="checkbox" id= "myCheck" [(ngModel)]= "check" > --> 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 b947b5e081fbeff0b8522e4ec38be87a6edf9cb4..aa6468be4985a8de87814b3c65591b1ff7ce7d7b 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 @@ -1,6 +1,6 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormControl, Validators } from '@angular/forms'; -import { Room } from '../../../../models/room'; +import { ProfanityFilter, Room } from '../../../../models/room'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { RoomDeleteComponent } from '../room-delete/room-delete.component'; import { NotificationService } from '../../../../services/util/notification.service'; @@ -19,8 +19,10 @@ import { RoomDeleted } from '../../../../models/events/room-deleted'; }) export class RoomEditComponent implements OnInit { editRoom: Room; - check: boolean = false; - profanityCheck = true; + check = false; + profanityCheck: boolean; + censorPartialWordsCheck: boolean; + censorLanguageSpecificCheck: boolean; roomNameFormControl = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(30)]); @@ -36,7 +38,13 @@ export class RoomEditComponent implements OnInit { ngOnInit() { this.check = this.editRoom.questionsBlocked; - this.profanityCheck = this.editRoom.profanityFilter; + this.profanityCheck = this.editRoom.profanityFilter !== ProfanityFilter.deactivated; + if (this.editRoom.profanityFilter === ProfanityFilter.all){ + this.censorLanguageSpecificCheck = this.censorPartialWordsCheck = true; + } else if (this.profanityCheck){ + this.censorLanguageSpecificCheck = this.editRoom.profanityFilter === ProfanityFilter.languageSpecific; + this.censorPartialWordsCheck = this.editRoom.profanityFilter === ProfanityFilter.partialWords; + } } openDeleteRoomDialog(): void { @@ -73,9 +81,15 @@ export class RoomEditComponent implements OnInit { save(): void { this.editRoom.questionsBlocked = this.check; - this.editRoom.profanityFilter = this.profanityCheck; - // temp solution until the backend is updated - localStorage.setItem('room-profanity-filter', (this.profanityCheck ? 'true' : 'false')); + this.editRoom.profanityFilter = this.profanityCheck ? ProfanityFilter.none : ProfanityFilter.deactivated; + if (this.profanityCheck) { + if (this.censorLanguageSpecificCheck && this.censorPartialWordsCheck) { + this.editRoom.profanityFilter = ProfanityFilter.all; + } else { + this.editRoom.profanityFilter = this.censorLanguageSpecificCheck ? ProfanityFilter.languageSpecific : ProfanityFilter.none; + this.editRoom.profanityFilter = this.censorPartialWordsCheck ? ProfanityFilter.partialWords : this.editRoom.profanityFilter; + } + } this.roomService.updateRoom(this.editRoom).subscribe(r => this.editRoom = r); if (!this.roomNameFormControl.hasError('required') && !this.roomNameFormControl.hasError('minlength') @@ -99,8 +113,8 @@ export class RoomEditComponent implements OnInit { return () => this.save(); } - showMessage(label?: string) { - if (this.profanityCheck){ + showMessage(label: string, event: boolean) { + if (event) { this.translationService.get('room-page.'+label).subscribe(msg => { this.notificationService.show(msg); }); diff --git a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts index fbd0782ff8e02645ab6e575d767ed97d1b8e5b95..854f7595a5f0ec417b4cb034b2964583ac93b828 100644 --- a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts +++ b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts @@ -326,7 +326,8 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy { c.id = payload.id; c.timestamp = payload.timestamp; c.creatorId = payload.creatorId; - c.keywordsFromQuestioner = JSON.parse(payload.keywordsFromQuestioner); + c.keywordsFromQuestioner = payload.keywordsFromQuestioner ? + JSON.parse(payload.keywordsFromQuestioner as unknown as string) : null; c.userNumber = this.commentService.hashCode(c.creatorId); this.comments = this.comments.concat(c); break; 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 b2a76fad7c816a9134ef5aa7675ec9627229b2d7..978d076a300d08cc545bd77890cacefcca1cc8b1 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 @@ -23,19 +23,15 @@ </div> <div class="anchor-wrp"> <div class="anchor-right"> - <mat-form-field *ngIf="tags" - class="tag-form-field"> + <mat-form-field *ngIf="tags" class="tag-form-field"> <mat-label> - <mat-icon class="icon-svg" - svgIcon="comment_tag"></mat-icon> - {{'comment-page.tag' | translate}}</mat-label> - <mat-select [(ngModel)]="selectedTag" - class="tag-select"> + <mat-icon class="icon-svg" svgIcon="comment_tag"></mat-icon> + {{'comment-page.tag' | translate}} + </mat-label> + <label for="tagSelect">{{selectedTag}}</label> + <mat-select [(ngModel)]="selectedTag" class="tag-select" id="tagSelect" style="display: inline"> <mat-option>{{'comment-page.tag-reset' | translate}}</mat-option> - <mat-option *ngFor="let tag of tags" - value="{{tag}}"> - {{tag}} - </mat-option> + <mat-option *ngFor="let tag of tags" value="{{tag}}">{{tag}}</mat-option> </mat-select> </mat-form-field> </div> @@ -102,7 +98,10 @@ </ars-row> <ars-row ars-flex-box class="spellcheck"> <ars-col> - <button mat-button class="spell-button" (click)="grammarCheck(commentBody)"> + <button + [disabled]="this.commentBody.innerHTML.length === 0" + mat-flat-button class="spell-button" + (click)="grammarCheck(commentBody)"> {{ 'comment-page.grammar-check' | translate}} <mat-icon *ngIf="isSpellchecking" style="margin: 0;"> <mat-spinner diameter="20"></mat-spinner> 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 d39c798de3c1a306f8b69596cff455f17c7b1a22..5ca59b3528a5543b164bdd09a603854fb9d0d20f 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,5 +1,5 @@ import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { Comment } from '../../../../models/comment'; +import { Comment, Language as CommentLanguage } from '../../../../models/comment'; import { NotificationService } from '../../../../services/util/notification.service'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; @@ -116,17 +116,18 @@ export class CreateCommentComponent implements OnInit, OnDestroy { } } - checkUTFEmoji(body: string): string { - const regex = /(?:\:.*?\:|[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g; - - return body.replace(regex, ''); - } - openSpacyDialog(comment: Comment): void { CreateCommentKeywords.isSpellingAcceptable(this.languagetoolService, this.inputText, this.selectedLang) .subscribe((result) => { if (result.isAcceptable) { const commentLang = this.languagetoolService.mapLanguageToSpacyModel(result.result.language.code as Language); + const selectedLangExtend = this.selectedLang[2] === '-' ? this.selectedLang.substr(0, 2) : this.selectedLang; + // Store language if it was auto-detected + if (this.selectedLang === 'auto') { + comment.language = Comment.mapModelToLanguage(commentLang); + } else if (CommentLanguage[selectedLangExtend]) { + comment.language = CommentLanguage[selectedLangExtend]; + } const dialogRef = this.dialog.open(SpacyDialogComponent, { data: { comment, @@ -140,6 +141,7 @@ export class CreateCommentComponent implements OnInit, OnDestroy { } }); } else { + comment.language = CommentLanguage.auto; this.dialogRef.close(comment); } this.isSendingToSpacy = false; @@ -161,7 +163,7 @@ export class CreateCommentComponent implements OnInit, OnDestroy { } checkSpellings(text: string, language: Language = this.selectedLang) { - return this.languagetoolService.checkSpellings(text, language); + return this.languagetoolService.checkSpellings(CreateCommentKeywords.cleaningFunction(text), language); } maxLength(commentBody: HTMLDivElement): void { diff --git a/src/app/components/shared/_dialogs/room-create/room-create.component.ts b/src/app/components/shared/_dialogs/room-create/room-create.component.ts index 191ce27da1750415a4ee897360169d3f11e6f2c0..f0f7a36d8a0d221f8464e6df56b9b59bbbaefc1f 100644 --- a/src/app/components/shared/_dialogs/room-create/room-create.component.ts +++ b/src/app/components/shared/_dialogs/room-create/room-create.component.ts @@ -1,13 +1,12 @@ import { Component, Inject, OnInit } from '@angular/core'; import { RoomService } from '../../../../services/http/room.service'; -import { Room } from '../../../../models/room'; +import { ProfanityFilter, Room } from '../../../../models/room'; import { UserRole } from '../../../../models/user-roles.enum'; import { Router } from '@angular/router'; import { NotificationService } from '../../../../services/util/notification.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AuthenticationService } from '../../../../services/http/authentication.service'; import { TranslateService } from '@ngx-translate/core'; -import { TSMap } from 'typescript-map'; import { EventService } from '../../../../services/util/event.service'; import { User } from '../../../../models/user'; @@ -76,7 +75,7 @@ export class RoomCreateComponent implements OnInit { newRoom.description = ''; newRoom.blacklist = '[]'; newRoom.questionsBlocked = false; - newRoom.profanityFilter = true; + newRoom.profanityFilter = ProfanityFilter.none; if (this.hasCustomShortId && this.customShortIdName && this.customShortIdName.length > 0) { if (!new RegExp('[1-9a-z,A-Z,\s,\-,\.,\_,\~]+').test(this.customShortIdName) || this.customShortIdName.startsWith(' ') || this.customShortIdName.endsWith(' ')) { @@ -100,8 +99,8 @@ export class RoomCreateComponent implements OnInit { this.room = room; let msg1: string; let msg2: string; - this.translateService.get('home-page.created-1').subscribe(msg => { msg1 = msg; }); - this.translateService.get('home-page.created-2').subscribe(msg => { msg2 = msg; }); + this.translateService.get('home-page.created-1').subscribe(msg => msg1 = msg); + this.translateService.get('home-page.created-2').subscribe(msg => msg2 = msg); this.notification.show(msg1 + longRoomName + msg2); this.authenticationService.setAccess(encoded, UserRole.CREATOR); this.authenticationService.assignRole(UserRole.CREATOR); 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 aebb707b53bdc0b78d2addd685d9820fc061bf5d..aadff087cfc38dcbd323e9067faa3f68484aba41 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 @@ -1,6 +1,7 @@ <ars-row> <div class="anchor-wrp"> <span *ngIf="keywords.length > 0"> + <span>{{ 'spacy-dialog.select-keywords' | translate }}</span> <ars-row class="select-all-section"> <mat-checkbox class="select-all-checkbox" id="checkAll" @@ -42,14 +43,14 @@ matTooltipShowDelay="750"> </mat-checkbox> <button *ngIf="!keyword.editing" - (click)="onEdit(keyword)" mat-icon-button + (click)="onEdit(keyword); onEditChange(1)" mat-icon-button [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 + (click)="onEndEditing(keyword); onEditChange(-1)" mat-icon-button class = "edit-accept" matTooltip="{{ 'spacy-dialog.editing-done-hint' | translate }}" matTooltipShowDelay="750"> @@ -75,6 +76,7 @@ <ars-fill></ars-fill> <ars-col> <app-dialog-action-buttons + #appDialogActionButtons buttonsLabelSection="comment-page" confirmButtonLabel="send" [showDivider]="false" 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 23ef9c3ebb2260255822b95ff9cb8277fe1f5f4d..45166229b358cbcc4c6a3c2c4765931d45931961 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 @@ -1,11 +1,12 @@ -import { AfterContentInit, Component, Inject, OnInit } from '@angular/core'; +import { AfterContentInit, Component, Inject, OnInit, ViewChild } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { CreateCommentComponent } from '../create-comment/create-comment.component'; import { SpacyService, Model } from '../../../../services/http/spacy.service'; -import { LanguageService } from '../../../../services/util/language.service'; +import { LanguagetoolService } from '../../../../services/http/languagetool.service'; import { Comment } from '../../../../models/comment'; import { map } from 'rxjs/operators'; +import { DialogActionButtonsComponent } from '../../dialog/dialog-action-buttons/dialog-action-buttons.component'; export interface Keyword { word: string; @@ -21,6 +22,8 @@ export interface Keyword { }) export class SpacyDialogComponent implements OnInit, AfterContentInit { + @ViewChild('appDialogActionButtons') appDialogActionButtons: DialogActionButtonsComponent; + comment: Comment; commentLang: Model; commentBodyChecked: string; @@ -29,9 +32,10 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { isLoading = false; langSupported: boolean; manualKeywords = ''; + _concurrentEdits = 0 constructor( - protected langService: LanguageService, + protected langService: LanguagetoolService, private spacyService: SpacyService, public dialogRef: MatDialogRef<CreateCommentComponent>, @Inject(MAT_DIALOG_DATA) public data) { @@ -41,7 +45,7 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { this.comment = this.data.comment; this.commentLang = this.data.commentLang; this.commentBodyChecked = this.data.commentBodyChecked; - this.langSupported = this.commentLang !== 'auto'; + this.langSupported = this.langService.isSupportedLanguage(this.data.commentLang); } ngAfterContentInit(): void { @@ -143,4 +147,9 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { this.keywords = []; } } + + onEditChange(change: number) { + this._concurrentEdits += change; + this.appDialogActionButtons.confirmButtonDisabled = (this._concurrentEdits > 0) + } } diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts index 56cd2858586d64983eba399683c3547cbf59c18a..e20969aa89fd13f8f857dc00e9461068e1e795d7 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts @@ -1,63 +1,65 @@ export interface TopicCloudAdminData { - blacklist: string[]; - wantedLabels: { - de: string[]; - en: string[]; - }; - considerVotes: boolean; - profanityFilter: boolean; - blacklistIsActive: boolean; - keywordORfulltext: KeywordOrFulltext; + blacklist: string[]; + wantedLabels: { + de: string[]; + en: string[]; + }; + considerVotes: boolean; + profanityFilter: boolean; + blacklistIsActive: boolean; + keywordORfulltext: KeywordOrFulltext; } export enum KeywordOrFulltext { - keyword, - fulltext, - both + keyword, + fulltext, + both } export interface Label { - readonly tag: string; - readonly label: string; + readonly tag: string; + readonly label: string; + readonly enabledByDefault: boolean; } export class Labels { - readonly de: Label[]; - readonly en: Label[]; + readonly de: Label[]; + readonly en: Label[]; - constructor(_de: Label[], _en: Label[]) { - this.de = _de; - this.en = _en; - } + constructor(_de: Label[], _en: Label[]) { + this.de = _de; + this.en = _en; + } } const deLabels: Label[] = [ - {tag: 'sb', label: 'Subjekt'}, - {tag: 'pd', label: 'Prädikat'}, - {tag: 'og', label: 'Genitivobjekt'}, - {tag: 'ag', label: 'Genitivattribut'}, - {tag: 'app', label: 'Apposition'}, - {tag: 'da', label: 'Dativobjekt'}, - {tag: 'oa', label: 'Akkusativobjekt'}, - {tag: 'nk', label: 'Nomen Kernelement'}, - {tag: 'mo', label: 'Modifikator'}, - {tag: 'cj', label: 'Konjunktor'}, - {tag: 'ROOT', label: 'Satzkernelement'}, - {tag: 'par', label: 'Klammerzusatz'} + {tag: 'sb', label: 'Subjekt', enabledByDefault: true}, + {tag: 'op', label: 'Präpositionalobjekt', enabledByDefault: true}, + {tag: 'og', label: 'Genitivobjekt', enabledByDefault: true}, + {tag: 'da', label: 'Dativobjekt', enabledByDefault: true}, + {tag: 'oa', label: 'Akkusativobjekt', enabledByDefault: true}, + {tag: 'pd', label: 'Prädikat', enabledByDefault: false}, + {tag: 'ag', label: 'Genitivattribut', enabledByDefault: false}, + {tag: 'app', label: 'Apposition', enabledByDefault: false}, + {tag: 'nk', label: 'Nomen Kernelement', enabledByDefault: false}, + {tag: 'mo', label: 'Modifikator', enabledByDefault: false}, + {tag: 'cj', label: 'Konjunktor', enabledByDefault: false}, + {tag: 'ROOT', label: 'Satzkernelement', enabledByDefault: false}, + {tag: 'par', label: 'Klammerzusatz', enabledByDefault: false} ]; const enLabels: Label[] = [ - {tag: 'nsubj', label: 'Nominal subject'}, - {tag: 'nsubjpass', label: 'Passive nominal subject'}, - {tag: 'pobj', label: 'Object of preposition'}, - {tag: 'nummod', label: 'Numeric modifier'}, - {tag: 'compound', label: 'Compound'}, - {tag: 'dobj', label: 'Direct object'}, - {tag: 'amod', label: 'Adjectival modifier'}, - {tag: 'npadvmod', label: 'Noun phrase as adverbial modifier'}, - {tag: 'conj', label: 'Conjunct'}, - {tag: 'ROOT', label: 'Sentence kernel element'}, - {tag: 'intj', label: 'Interjection'} + {tag: 'nsubj', label: 'Nominal subject', enabledByDefault: true}, + {tag: 'pobj', label: 'Object of preposition', enabledByDefault: true}, + {tag: 'dobj', label: 'Direct object', enabledByDefault: true}, + {tag: 'compound', label: 'Compound', enabledByDefault: true}, + {tag: 'nsubjpass', label: 'Passive nominal subject', enabledByDefault: true}, + {tag: 'nummod', label: 'Numeric modifier', enabledByDefault: false}, + {tag: 'amod', label: 'Adjectival modifier', enabledByDefault: false}, + {tag: 'npadvmod', label: 'Noun phrase as adverbial modifier', enabledByDefault: false}, + {tag: 'conj', label: 'Conjunct', enabledByDefault: false}, + {tag: 'ROOT', label: 'Sentence kernel element', enabledByDefault: false}, + {tag: 'intj', label: 'Interjection', enabledByDefault: false} ]; export const spacyLabels = new Labels(deLabels, enLabels); diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html index d825f6650f7d90a4aee98e0c7e1c92658e10f1f0..2f918ec39dea22417943f1dc7ac40536aef1f9a8 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 @@ -211,8 +211,8 @@ <mat-accordion> <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"> + *ngFor="let keyword of (searchMode ? filteredKeywords : keywords); let i = index" [attr.data-index]="i" + matTooltip="{{'topic-cloud-dialog.'+(keyword.keywordType === 0?'keyword-from-spacy':'keyword-from-questioner') | translate}}"> <mat-expansion-panel-header class="color-surface"> <mat-panel-title> {{profanityFilter ? keyword.keywordWithoutProfanity : keyword.keyword}} 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 5a613e2705c76b45501f5f06557ce897d87c0281..5088205464787d718e56e630b759bfbdff170fb7 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 @@ -102,10 +102,9 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } refreshKeywords() { - const tempKeywords = this.keywords; this.keywords = []; - tempKeywords.forEach(keyword => { - keyword.comments.forEach(comment => this.pushInKeywords(comment)); + this.roomDataService.currentRoomData.forEach(comment => { + this.pushInKeywords(comment); }); if (this.searchMode) { this.searchKeyword(); @@ -113,13 +112,16 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } pushInKeywords(comment: Comment) { + let _keywordType = KeywordType.fromQuestioner; let keywords = comment.keywordsFromQuestioner; if (this.keywordORfulltext === KeywordOrFulltext[KeywordOrFulltext.both]) { if (!keywords || !keywords.length) { keywords = comment.keywordsFromSpacy; + _keywordType = KeywordType.fromSpacy; } } else if (this.keywordORfulltext === KeywordOrFulltext[KeywordOrFulltext.fulltext]) { keywords = comment.keywordsFromSpacy; + _keywordType = KeywordType.fromSpacy; } if (!keywords) { keywords = []; @@ -134,6 +136,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } else { const keyword: Keyword = { keyword: _keyword, + keywordType: _keywordType, keywordWithoutProfanity: this.getKeywordWithoutProfanity(_keyword), comments: [comment], vote: comment.score @@ -227,7 +230,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } getKeywordWithoutProfanity(keyword: string): string { - return this.topicCloudAdminService.filterProfanityWords(keyword); + return this.topicCloudAdminService.filterProfanityWords(keyword, true, false); } sortQuestions(sortMode?: string) { @@ -450,6 +453,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { interface Keyword { keyword: string; + keywordType: KeywordType; keywordWithoutProfanity: string; comments: Comment[]; vote: number; @@ -459,3 +463,8 @@ export interface Data { user: User; } +enum KeywordType { + fromSpacy = 0, + fromQuestioner = 1 +} + 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 8838a59c151580b2f44d30605c35a1a93f4b7c81..3b1911cb570fcfb476624a32042e1763ed9859f8 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 @@ -1,30 +1,32 @@ -<div mat-dialog-content> - <p>{{'content.topic-cloud-content' | translate}}</p> -</div> - <mat-divider></mat-divider> <mat-radio-group [(ngModel)]="continueFilter" aria-label="Select an option"> - <mat-radio-button value="continueWithAll"> + <mat-radio-button checked="true" 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> {{allComments.comments}} - <mat-icon [inline]="true">person</mat-icon> {{allComments.users}} - <mat-icon svgIcon="hashtag" class="comment_tag-icon"></mat-icon> {{allComments.keywords}} + <mat-icon [inline]="true" + matTooltip="{{'header.overview-question-tooltip' | translate}}">comment</mat-icon> {{allComments.comments}} + <mat-icon [inline]="true" + matTooltip="{{'header.overview-questioners-tooltip' | translate}}">person</mat-icon> {{allComments.users}} + <mat-icon svgIcon="hashtag" class="comment_tag-icon" + matTooltip="{{'header.overview-keywords-tooltip' | translate}}"></mat-icon> {{allComments.keywords}} </div> </div> </mat-radio-button> - <mat-radio-button checked="true" value="continueWithCurr" *ngIf="!disableCurrentFiltersOptions"> + <mat-radio-button value="continueWithCurr" *ngIf="!disableCurrentFiltersOptions"> <div class="elementRow"> <div class="elementText"> {{'content.continue-with-current-questions' | translate}} </div> <div class="elementIcons"> - <mat-icon [inline]="true">comment</mat-icon> {{filteredComments.comments}} - <mat-icon [inline]="true">person</mat-icon> {{filteredComments.users}} - <mat-icon svgIcon="hashtag" class="comment_tag-icon"></mat-icon> {{filteredComments.keywords}} + <mat-icon [inline]="true" + matTooltip="{{'header.overview-question-tooltip' | translate}}">comment</mat-icon> {{filteredComments.comments}} + <mat-icon [inline]="true" + matTooltip="{{'header.overview-questioners-tooltip' | translate}}">person</mat-icon> {{filteredComments.users}} + <mat-icon svgIcon="hashtag" class="comment_tag-icon" + matTooltip="{{'header.overview-keywords-tooltip' | translate}}"></mat-icon> {{filteredComments.keywords}} </div> </div> </mat-radio-button> 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 f86b23334a449a41725206123391f314cabfeffd..2cce403e9e7d0e084aaff4bfb6af3ed7de2ab23f 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 @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit, Input } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { NotificationService } from '../../../../services/util/notification.service'; import { TranslateService } from '@ngx-translate/core'; @@ -10,6 +10,8 @@ import { CommentFilter } from '../../../../utils/filter-options'; import { RoomService } from '../../../../services/http/room.service'; import { Comment } from '../../../../models/comment'; import { CommentListData } from '../../comment-list/comment-list.component'; +import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; +import { KeywordOrFulltext } from '../topic-cloud-administration/TopicCloudAdminData'; class CommentsCount { comments: number; @@ -23,14 +25,16 @@ class CommentsCount { styleUrls: ['./topic-cloud-filter.component.scss'] }) export class TopicCloudFilterComponent implements OnInit { - @Input() shortId: string; + @Input() target: string; - continueFilter = 'continueWithCurr'; + continueFilter = 'continueWithAll'; comments: Comment[]; tmpFilter: CommentFilter; allComments: CommentsCount; filteredComments: CommentsCount; disableCurrentFiltersOptions = false; + private readonly _filter: KeywordOrFulltext; + private readonly _blacklist: string[]; constructor(public dialogRef: MatDialogRef<RoomCreatorPageComponent>, public dialog: MatDialog, @@ -42,6 +46,9 @@ export class TopicCloudFilterComponent implements OnInit { @Inject(MAT_DIALOG_DATA) public data: any, public eventService: EventService) { langService.langEmitter.subscribe(lang => translationService.use(lang)); + const adminData = TopicCloudAdminService.getDefaultAdminData; + this._filter = adminData.keywordORfulltext; + this._blacklist = adminData.blacklist; } ngOnInit() { @@ -76,9 +83,25 @@ export class TopicCloudFilterComponent implements OnInit { if (c.userNumber) { userSet.add(c.userNumber); } - if (c.keywordsFromQuestioner) { - c.keywordsFromQuestioner.forEach(k => { - keywordSet.add(k); + let source = c.keywordsFromQuestioner; + if (this._filter === KeywordOrFulltext.both) { + source = !source || !source.length ? c.keywordsFromSpacy : source; + } else if (this._filter === KeywordOrFulltext.fulltext) { + source = c.keywordsFromSpacy; + } + if (source) { + source.forEach(k => { + let isProfanity = false; + const lowerCasedKeyword = k.toLowerCase(); + for (const word of this._blacklist) { + if (lowerCasedKeyword.includes(word)) { + isProfanity = true; + break; + } + } + if (!isProfanity) { + keywordSet.add(k); + } }); } }); @@ -115,7 +138,7 @@ export class TopicCloudFilterComponent implements OnInit { } CommentFilter.currentFilter = filter; - this.dialogRef.close(this.router.navigateByUrl('/participant/room/' + this.shortId + '/comments/tagcloud')); + this.dialogRef.close(this.router.navigateByUrl(this.target)); }; } } diff --git a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog-task.ts b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog-task.ts index 4a9613780829439612b4b081d0362dc6129ab69b..5bfdb4b459ff80efb336bd8635a6daed705b248d 100644 --- a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog-task.ts +++ b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog-task.ts @@ -11,7 +11,6 @@ const concurrentCallsPerTask = 4; export class WorkerDialogTask { - initializing = true; error: string = null; readonly statistics = { succeeded: 0, @@ -19,24 +18,26 @@ export class WorkerDialogTask { failed: 0, length: 0 }; - private _comments: Comment[] = null; - private _running: boolean[] = null; + private readonly _comments: Comment[] = null; + private readonly _running: boolean[] = null; constructor(public readonly room: Room, + private comments: Comment[], private spacyService: SpacyService, private commentService: CommentService, private languagetoolService: LanguagetoolService, private finished: () => void) { - this.commentService.getAckComments(room.id).subscribe((c) => { - this._comments = c; - this.statistics.length = c.length; - this.initializing = false; - this._running = new Array(concurrentCallsPerTask); - for (let i = 0; i < concurrentCallsPerTask; i++) { - this._running[i] = true; - this.callSpacy(i); - } - }); + this._comments = comments; + this.statistics.length = comments.length; + this._running = new Array(concurrentCallsPerTask); + for (let i = 0; i < concurrentCallsPerTask; i++) { + this._running[i] = true; + this.callSpacy(i); + } + } + + isRunning(): boolean { + return this._running.some(e => e === true); } private callSpacy(currentIndex: number) { @@ -50,7 +51,6 @@ export class WorkerDialogTask { } return; } - const fallbackmodel = (localStorage.getItem('currentLang') || 'de') as Model; const currentComment = this._comments[currentIndex]; CreateCommentKeywords.isSpellingAcceptable(this.languagetoolService, currentComment.body) .subscribe(result => { @@ -59,9 +59,15 @@ export class WorkerDialogTask { this.callSpacy(currentIndex + concurrentCallsPerTask); return; } - const model = this.languagetoolService - .mapLanguageToSpacyModel(result.result.language.detectedLanguage.code as Language); - this.spacyService.getKeywords(result.text, model === 'auto' ? fallbackmodel : model) + const commentModel = currentComment.language.toLowerCase(); + const model = commentModel !== 'auto' ? commentModel.toLowerCase() as Model : + this.languagetoolService.mapLanguageToSpacyModel(result.result.language.detectedLanguage.code as Language); + if (model === 'auto') { + this.statistics.badSpelled++; + this.callSpacy(currentIndex + concurrentCallsPerTask); + return; + } + this.spacyService.getKeywords(result.text, model) .subscribe(newKeywords => { const changes = new TSMap<string, string>(); changes.set('keywordsFromSpacy', JSON.stringify(newKeywords)); @@ -73,19 +79,16 @@ export class WorkerDialogTask { if (patchError instanceof HttpErrorResponse && patchError.status === 403) { this.error = 'forbidden'; } - console.log(patchError); }, () => { this.callSpacy(currentIndex + concurrentCallsPerTask); }); }, - keywordError => { + __ => { this.statistics.failed++; - console.log(keywordError); this.callSpacy(currentIndex + concurrentCallsPerTask); }); - }, error => { + }, _ => { this.statistics.failed++; - console.log(error); this.callSpacy(currentIndex + concurrentCallsPerTask); }); } diff --git a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.html b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.html index a57273c93fc28a10d7c6b051ae2a85da832af9f7..09e7320f9523027e0795585fc6197f2a992c5c7a 100644 --- a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.html +++ b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.html @@ -2,16 +2,30 @@ <div id="header" (window:beforeunload)="checkTasks($event)"> <details> <summary> - <span>{{'worker-dialog.running' | translate}} # {{getRooms().length}}</span> + <span>{{'worker-dialog.running' | translate}} # {{getActiveRoomCount()}}</span> <span><button id="btn_hide" (click)="close()">x</button></span> </summary> <div mat-dialog-content> - <div id="entry" *ngFor="let task of getRooms().values()"> - <mat-icon svgIcon="meeting_room"></mat-icon> - <span>{{ task.room.name }}</span> - <span style="width: 10px"></span> - <mat-icon>comment</mat-icon> - <span>{{ task.statistics.length }}</span> + <div class="entry" *ngFor="let task of getRooms().values()"> + <div class="entryRow"> + <mat-icon svgIcon="meeting_room" matTooltip="{{'worker-dialog.room-name' | translate}}"></mat-icon> + <span>{{ task.room.name }}</span> + <span *ngIf="task.error" style="color: var(--red)">{{task.error}}</span> + </div> + <div *ngIf="!task.error" class="entryRow"> + <mat-icon matTooltip="{{'worker-dialog.comments' | translate}}">comment</mat-icon> + <span>{{task.statistics.succeeded}}/{{ task.statistics.length}}</span> + <mat-icon *ngIf="task.statistics.badSpelled" style="color: darkorange" + matTooltip="{{'worker-dialog.bad-spelled' | translate}}">warning</mat-icon> + <span *ngIf="task.statistics.badSpelled"> + {{task.statistics.badSpelled}} + </span> + <mat-icon *ngIf="task.statistics.failed" style="color: var(--red)" + matTooltip="{{'worker-dialog.failed' | translate}}">error</mat-icon> + <span *ngIf="task.statistics.failed"> + {{task.statistics.failed}} + </span> + </div> </div> </div> </details> diff --git a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.scss b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.scss index b54f09a4c979f2b7c67c5a7d3e32fc6f87aac298..5ae6bb9888cac44a137dabd116bbb10f16e38bce 100644 --- a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.scss +++ b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.scss @@ -22,19 +22,33 @@ button { padding-left: 5px; } -summary { +.entry { + display: flex; + flex-direction: column; + justify-content: left; + border-bottom: var(--primary) dashed 2px; + margin-bottom: 3px; + padding-bottom: 3px; +} + +.entryRow { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; +} + +span, mat-icon { + margin-right: 7px; +} + +details > summary { border: none; outline: none; - box-shadow: 0 4px 4px -2px #232323; - -moz-box-shadow: 0 4px 4px -2px #232323; - -webkit-box-shadow: 0 4px 4px -2px #232323; cursor: pointer; } -#entry { - display:flex; - flex-direction:row; - justify-content: left; - } - - +details[open]> summary { + box-shadow: 0 4px 4px -2px #232323; + margin-bottom: 7px; +} diff --git a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.ts b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.ts index bf1621a61c1b02602a88b7259c2ec0a0f2c6acec..40c69bc9afd546fbaaf63a161095bdde52661205 100644 --- a/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.ts +++ b/src/app/components/shared/_dialogs/worker-dialog/worker-dialog.component.ts @@ -6,6 +6,10 @@ import { TSMap } from 'typescript-map'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { WorkerDialogTask } from './worker-dialog-task'; import { LanguagetoolService } from '../../../../services/http/languagetool.service'; +import { TranslateService } from '@ngx-translate/core'; +import { LanguageService } from '../../../../services/util/language.service'; +import { Comment } from '../../../../models/comment'; +import { RoomDataService } from '../../../../services/util/room-data.service'; @Component({ selector: 'app-worker-dialog', @@ -19,7 +23,11 @@ export class WorkerDialogComponent implements OnInit { constructor(private commentService: CommentService, private languagetoolService: LanguagetoolService, - private spacyService: SpacyService) { + private spacyService: SpacyService, + protected langService: LanguageService, + private translateService: TranslateService, + private roomDataService: RoomDataService) { + langService.langEmitter.subscribe(lang => translateService.use(lang)); } static addWorkTask(dialog: MatDialog, room: Room): boolean { @@ -44,11 +52,12 @@ export class WorkerDialogComponent implements OnInit { if (this.queuedRooms.has(room.id)) { return false; } - this.dialogRef.componentInstance.appendRoom(room); + this.dialogRef.componentInstance.appendRoom(room, this.dialogRef.componentInstance.roomDataService.currentRoomData); return true; } ngOnInit(): void { + this.translateService.use(localStorage.getItem('currentLang')); } checkTasks(event: BeforeUnloadEvent) { @@ -62,12 +71,25 @@ export class WorkerDialogComponent implements OnInit { return WorkerDialogComponent.queuedRooms; } - appendRoom(room: Room) { + getActiveRoomCount(): number { + let count = 0; + WorkerDialogComponent.queuedRooms.values().forEach(e => { + if (e.isRunning()) { + ++count; + } + }); + return count; + } + + appendRoom(room: Room, comments: Comment[]) { WorkerDialogComponent.queuedRooms.set(room.id, - new WorkerDialogTask(room, this.spacyService, this.commentService, this.languagetoolService, () => { - if (WorkerDialogComponent.queuedRooms.length === 0) { - setTimeout(() => this.close(), 2000); - } + new WorkerDialogTask(room, comments, this.spacyService, this.commentService, this.languagetoolService, () => { + setTimeout(() => { + WorkerDialogComponent.queuedRooms.delete(room.id); + if (WorkerDialogComponent.queuedRooms.length === 0) { + this.close(); + } + }, 10_000); }) ); } diff --git a/src/app/components/shared/comment-answer/comment-answer.component.ts b/src/app/components/shared/comment-answer/comment-answer.component.ts index 7b8ce1a71568d2470b9ec659a4a9a8ce31a6dec2..c4641fc14f04fdac912d98ff94a15a935f9161e2 100644 --- a/src/app/components/shared/comment-answer/comment-answer.component.ts +++ b/src/app/components/shared/comment-answer/comment-answer.component.ts @@ -11,6 +11,7 @@ import { UserRole } from '../../../models/user-roles.enum'; import { NotificationService } from '../../../services/util/notification.service'; import { MatDialog } from '@angular/material/dialog'; import { DeleteAnswerComponent } from '../../creator/_dialogs/delete-answer/delete-answer.component'; +import { RoomDataService } from '../../../services/util/room-data.service'; @Component({ selector: 'app-comment-answer', @@ -33,6 +34,7 @@ export class CommentAnswerComponent implements OnInit { protected wsCommentService: WsCommentService, protected commentService: CommentService, private authenticationService: AuthenticationService, + private roomDataService: RoomDataService, public dialog: MatDialog) { } ngOnInit() { 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 a346dd2a1784d11facede53cc5973348a954592b..5edfdf4716cec1f1fe40eed822c598995f1d7808 100644 --- a/src/app/components/shared/comment-list/comment-list.component.html +++ b/src/app/components/shared/comment-list/comment-list.component.html @@ -30,7 +30,7 @@ mat-icon-button class="searchBarButton close red" *ngIf="searchInput !== '' || search" - (click)="hideCommentsList=false; searchInput = ''; search = false; searchPlaceholder = '';" + (click)="hideCommentsList=false; searchInput = ''; search = false;" aria-labelledby="close_search"> <mat-icon>close</mat-icon> </button> diff --git a/src/app/components/shared/comment-list/comment-list.component.ts b/src/app/components/shared/comment-list/comment-list.component.ts index 7f908387c67f44a0c02dfd53054de1d51afa4b3a..f1c5032d3f8c00dc828e2c0036222822cf299606 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -29,6 +29,7 @@ import { CommentFilter, Period } from '../../../utils/filter-options'; import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper'; import { TopicCloudAdminService } from '../../../services/util/topic-cloud-admin.service'; import { RoomDataService } from '../../../services/util/room-data.service'; +import { WsRoomService } from '../../../services/websockets/ws-room.service'; export interface CommentListData { comments: Comment[]; @@ -95,9 +96,11 @@ export class CommentListComponent implements OnInit, OnDestroy { fromNow: number; moderatorIds: string[]; commentsEnabled: boolean; + userNumberSelection: number = 0; createCommentWrapper: CreateCommentWrapper = null; private _subscriptionEventServiceTagConfig = null; private _subscriptionEventServiceRoomData = null; + private _subscriptionRoomService = null; constructor( private commentService: CommentService, @@ -117,9 +120,15 @@ export class CommentListComponent implements OnInit, OnDestroy { private bonusTokenService: BonusTokenService, private moderatorService: ModeratorService, private topicCloudAdminService: TopicCloudAdminService, - private roomDataService: RoomDataService + private roomDataService: RoomDataService, + private wsRoomService: WsRoomService ) { - langService.langEmitter.subscribe(lang => translateService.use(lang)); + langService.langEmitter.subscribe(lang => { + translateService.use(lang); + this.translateService.get('comment-list.search').subscribe(msg => { + this.searchPlaceholder = msg; + }); + }); } initNavigation() { @@ -221,6 +230,7 @@ export class CommentListComponent implements OnInit, OnDestroy { this.userRole = this.route.snapshot.data.roles[0]; this.route.params.subscribe(params => { this.shortId = params['shortId']; + this.authenticationService.checkAccess(this.shortId); this.authenticationService.guestLogin(UserRole.PARTICIPANT).subscribe(r => { this.roomService.getRoomByShortId(this.shortId).subscribe(room => { this.room = room; @@ -262,12 +272,23 @@ export class CommentListComponent implements OnInit, OnDestroy { this.translateService.get('comment-list.search').subscribe(msg => { this.searchPlaceholder = msg; }); + this._subscriptionRoomService = this.wsRoomService.getRoomStream(this.roomId).subscribe(msg => { + const message = JSON.parse(msg.body); + if (message.type === 'RoomPatched') { + this.room = message.payload.changes; + this.roomId = this.room.id; + this.moderationEnabled = this.room.moderated; + this.directSend = this.room.directSend; + this.commentsEnabled = (this.userRole > 0) || !this.room.questionsBlocked; + } + }); } ngOnDestroy() { if (!this.freeze && this.commentStream) { this.commentStream.unsubscribe(); } + this._subscriptionRoomService.unsubscribe(); this.titleService.resetTitle(); if (this.headerInterface) { this.headerInterface.unsubscribe(); @@ -309,9 +330,6 @@ export class CommentListComponent implements OnInit, OnDestroy { } activateSearch() { - this.translateService.get('comment-list.search').subscribe(msg => { - this.searchPlaceholder = msg; - }); this.search = true; this.searchField.nativeElement.focus(); } @@ -431,6 +449,7 @@ export class CommentListComponent implements OnInit, OnDestroy { } clickedUserNumber(usrNumber: number): void { + this.userNumberSelection = usrNumber; this.filterComments(this.userNumber, usrNumber); } @@ -570,6 +589,7 @@ export class CommentListComponent implements OnInit, OnDestroy { filter.periodSet = this.period; filter.keywordSelected = this.selectedKeyword; filter.tagSelected = this.selectedTag; + filter.userNumberSelected = this.userNumberSelection; if (filter.periodSet === Period.fromNow) { filter.timeStampNow = new Date().getTime(); diff --git a/src/app/components/shared/comment/comment.component.scss b/src/app/components/shared/comment/comment.component.scss index b4f6c773b849c1b862afc4a41aba2f0dc61ad03b..88dce68e1e06883ec93e92dfb106709b564953f9 100644 --- a/src/app/components/shared/comment/comment.component.scss +++ b/src/app/components/shared/comment/comment.component.scss @@ -206,6 +206,10 @@ mat-card-content > :first-child { height: 18px !important; } +.mat-badge-content { + background: #fb9a1c; +} + .user-number { cursor: pointer; color: var(--on-surface); diff --git a/src/app/components/shared/comment/comment.component.ts b/src/app/components/shared/comment/comment.component.ts index f3e688c7c685212c3e065a51acf81e775a04c15e..c8928f42f761bcb5a853408ec4afa5ef806ef354 100644 --- a/src/app/components/shared/comment/comment.component.ts +++ b/src/app/components/shared/comment/comment.component.ts @@ -17,6 +17,7 @@ import { UserRole } from '../../../models/user-roles.enum'; import { Rescale } from '../../../models/rescale'; import { RowComponent } from '../../../../../projects/ars/src/lib/components/layout/frame/row/row.component'; import { User } from '../../../models/user'; +import { RoomDataService } from '../../../services/util/room-data.service'; @Component({ selector: 'app-comment', @@ -67,6 +68,7 @@ export class CommentComponent implements OnInit, AfterViewInit { private commentService: CommentService, private notification: NotificationService, private translateService: TranslateService, + private roomDataService: RoomDataService, public dialog: MatDialog, protected langService: LanguageService) { langService.langEmitter.subscribe(lang => { @@ -76,6 +78,7 @@ export class CommentComponent implements OnInit, AfterViewInit { } ngOnInit() { + this.checkProfanity(); switch (this.userRole) { case UserRole.PARTICIPANT.valueOf(): this.isStudent = true; @@ -95,6 +98,12 @@ export class CommentComponent implements OnInit, AfterViewInit { this.inAnswerView = !this.router.url.includes('comments'); } + checkProfanity(){ + if (!this.router.url.includes('moderator/comments')) { + this.roomDataService.checkProfanity(this.comment); + } + } + ngAfterViewInit(): void { this.isExpandable = this.commentBody.getRenderedHeight() > CommentComponent.COMMENT_MAX_HEIGHT; if (!this.isExpandable) { @@ -128,10 +137,10 @@ export class CommentComponent implements OnInit, AfterViewInit { } setRead(comment: Comment): void { - // @ts-ignore - - this.commentService.toggleRead(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)}); - // @ts-ignore + this.commentService.toggleRead(comment).subscribe(c => { + this.comment = c; + this.checkProfanity(); + }); } markCorrect(comment: Comment, type: CorrectWrong): void { @@ -140,16 +149,18 @@ export class CommentComponent implements OnInit, AfterViewInit { } else { comment.correct = type; } - // @ts-ignore - this.commentService.markCorrect(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)}); - // @ts-ignore + this.commentService.markCorrect(comment).subscribe(c => { + this.comment = c; + this.checkProfanity(); + }); } setFavorite(comment: Comment): void { - // @ts-ignore - this.commentService.toggleFavorite(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)}); - // @ts-ignore + this.commentService.toggleFavorite(comment).subscribe(c => { + this.comment = c; + this.checkProfanity(); + }); } voteUp(comment: Comment): void { @@ -216,15 +227,17 @@ export class CommentComponent implements OnInit, AfterViewInit { } setAck(comment: Comment): void { - //@ts-ignore - this.commentService.toggleAck(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)}); - //@ts-ignore + this.commentService.toggleAck(comment).subscribe(c => { + this.comment = c; + this.checkProfanity(); + }); } setBookmark(comment: Comment): void { - //@ts-ignore - this.commentService.toggleBookmark(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)}); - //@ts-ignore + this.commentService.toggleBookmark(comment).subscribe(c => { + this.comment = c; + this.checkProfanity(); + }); } goToFullScreen(element: Element): void { 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 9ae1ae502f68b18599444df4143b78f61c33e031..8dab567c3e7da153906678740f074605b9cb93a4 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 @@ -6,6 +6,7 @@ <div fxLayoutAlign="end"> <div fxLayout="row-reverse" fxLayoutGap="10px" class="buttons"> <button + [disabled]="confirmButtonDisabled" *ngIf="confirmButtonClickAction !== undefined" type="button" mat-flat-button @@ -18,6 +19,7 @@ </mat-icon> </button> <button + [disabled]="cancelButtonDisabled" *ngIf="cancelButtonClickAction !== undefined" type="button" mat-flat-button diff --git a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts index 6b4af6f658edb828d083568cfc3d33fc6786b48b..56c12bac7b2c8e2a2135e7bec99ef26213677ee7 100644 --- a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts +++ b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts @@ -16,6 +16,16 @@ export enum DialogConfirmActionButtonType { }) export class DialogActionButtonsComponent implements OnInit { + /** + * gray out confirm button + */ + @Input() confirmButtonDisabled: boolean = false + + /** + * gray out cancel button + */ + @Input() cancelButtonDisabled: boolean = false + @Input() showDivider = true; /** diff --git a/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts b/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts index 0cb89b2aebbb7a1813ecdbedf859c5f1fb82d65a..2c58cb257810e1642c127b8af2f712a3d166b7fe 100644 --- a/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts +++ b/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts @@ -43,7 +43,7 @@ export class TopicDialogCommentComponent implements OnInit { } ngOnInit(): void { - this.questionWithoutProfanity = this.topicCloudAdminService.filterProfanityWords(this.question); + this.questionWithoutProfanity = this.topicCloudAdminService.filterProfanityWords(this.question, true, false); this.partsWithoutProfanity = this.splitQuestion(this.questionWithoutProfanity); this.parts = this.splitQuestion(this.question); this.partsWithoutProfanityShort = this.splitShortQuestion(this.questionWithoutProfanity); diff --git a/src/app/components/shared/footer/footer.component.html b/src/app/components/shared/footer/footer.component.html index dd8f018bdbd26b6e04430044ba0739cc74a0eac5..07c4ac5f8a91a7d2a7a01bf5385d8ca3e1a46e55 100644 --- a/src/app/components/shared/footer/footer.component.html +++ b/src/app/components/shared/footer/footer.component.html @@ -27,6 +27,7 @@ <span class="fill-remaining-space"></span> <button [matMenuTriggerFor]="langMenu" + (menuOpened)="openMenu()" aria-labelledby="language-label" class="focus_button" id="language-menu" diff --git a/src/app/components/shared/footer/footer.component.ts b/src/app/components/shared/footer/footer.component.ts index 2af9c97542e300c39ccd7da357725bd7790811b2..6cba039d5c54209b096782eb9ae1d556d9de4149 100644 --- a/src/app/components/shared/footer/footer.component.ts +++ b/src/app/components/shared/footer/footer.component.ts @@ -1,5 +1,5 @@ -import { LanguageService } from './../../../services/util/language.service'; -import { Component, OnInit } from '@angular/core'; +import { LanguageService } from '../../../services/util/language.service'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { NotificationService } from '../../../services/util/notification.service'; import { Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; @@ -18,6 +18,7 @@ import { AppComponent } from '../../../app.component'; import { StyleService } from '../../../../../projects/ars/src/lib/style/style.service'; import { MotdService } from '../../../services/http/motd.service'; import { MotdDialogComponent } from '../_dialogs/motd-dialog/motd-dialog.component'; +import { MatMenu } from '@angular/material/menu'; @Component({ selector: 'app-footer', @@ -26,6 +27,8 @@ import { MotdDialogComponent } from '../_dialogs/motd-dialog/motd-dialog.compone }) export class FooterComponent implements OnInit { + @ViewChild('langMenu') langaugeMenu: MatMenu; + public demoId = 'Feedback'; public room: Room; @@ -177,4 +180,12 @@ export class FooterComponent implements OnInit { getLanguage(): string { return localStorage.getItem('currentLang'); } + + openMenu() { + if (this.getLanguage() === 'de') { + this.langaugeMenu._allItems.get(0).focus(); + } else if (this.getLanguage() === 'en') { + this.langaugeMenu._allItems.get(1).focus(); + } + } } diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index a896aa9a99812982272d9f0fc0dcb56886ecd897..6477bdd39727ee736b92541bd617f028eb6f371c 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -17,7 +17,7 @@ </h2> - <ng-container *ngIf="room && room.closed && !router.url.includes('/room-list/')"> + <ng-container *ngIf="room && room.questionsBlocked"> <mat-icon>block</mat-icon><h1>{{'header.questions-blocked'|translate}}</h1> </ng-container> @@ -125,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 && !room.closed))" + *ngIf="user && !router.url.endsWith('moderator/comments') && ((user.role > 0) || ((user.role == 0) && room && !room.questionsBlocked))" tabindex="0" (click)="navigateCreateQuestion();"> <mat-icon> @@ -360,13 +360,14 @@ <span>{{'header.delete-account' | translate}}</span> </button> - <ng-container *ngIf="router.url.includes('/creator') || router.url.includes('/moderator')"> + <ng-container *ngIf="room && user && user.role > 0"> <button mat-menu-item (click)="blockQuestions()" - [ngClass]="{'color-warn': room && room.closed}" + [ngClass]="{'color-warn': room && room.questionsBlocked}" tabindex="0"> <mat-icon class="color-warn">block</mat-icon> - <span>{{'header.block' | translate}}</span> + <span *ngIf="!room.questionsBlocked">{{'header.block' | translate}}</span> + <span *ngIf="room.questionsBlocked">{{'header.unlock' | translate}}</span> </button> </ng-container> diff --git a/src/app/components/shared/header/header.component.scss b/src/app/components/shared/header/header.component.scss index 616d47238794e9ec610e9b7c3ccd752ebbe45acd..55b29273bbecdddcf6958e64f45ade386b888495 100644 --- a/src/app/components/shared/header/header.component.scss +++ b/src/app/components/shared/header/header.component.scss @@ -122,7 +122,27 @@ svg { * { background-color: transparent !important; } + *:hover { + color: var(--dialog); + background-color: var(--on-dialog) !important; + + mat-icon{ + color: var(--dialog); + background-color: var(--on-dialog) !important; + } + } + } -h1{ +h1 { color: red; } +::ng-deep .mat-menu-item:hover .qrcode svg > path { + fill: var(--dialog); + color: var(--dialog); + background-color: var(--on-dialog) !important; +} + +::ng-deep #cdk-overlay-0 .mat-menu-content > button:hover mat-icon { + color: var(--dialog); + background-color: var(--on-dialog) !important; +} diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts index 41b092c9a233358cdd7e6fb7f96b373f465be5c2..55a70f0b2d9b054854dcb4199885cccd043da212 100644 --- a/src/app/components/shared/header/header.component.ts +++ b/src/app/components/shared/header/header.component.ts @@ -25,6 +25,7 @@ import { RoomService } from '../../../services/http/room.service'; import { Room } from '../../../models/room'; import { TagCloudMetaData } from '../../../services/util/tag-cloud-data.service'; import { WorkerDialogComponent } from '../_dialogs/worker-dialog/worker-dialog.component'; +import { WsRoomService } from '../../../services/websockets/ws-room.service'; @Component({ selector: 'app-header', @@ -43,6 +44,7 @@ export class HeaderComponent implements OnInit { commentsCountQuestions = 0; commentsCountUsers = 0; commentsCountKeywords = 0; + private _subscriptionRoomService = null; constructor(public location: Location, private authenticationService: AuthenticationService, @@ -56,7 +58,8 @@ export class HeaderComponent implements OnInit { private _r: Renderer2, private motdService: MotdService, private confirmDialog: MatDialog, - private roomService: RoomService + private roomService: RoomService, + private wsRoomService: WsRoomService ) { } @@ -118,12 +121,25 @@ export class HeaderComponent implements OnInit { const segments = this.router.parseUrl(this.router.url).root.children.primary.segments; this.shortId = ''; this.room = null; + if (this._subscriptionRoomService) { + this._subscriptionRoomService.unsubscribe(); + this._subscriptionRoomService = 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); + this.roomService.getRoomByShortId(this.shortId).subscribe(room => { + this.room = room; + this._subscriptionRoomService = this.wsRoomService.getRoomStream(this.room.id).subscribe(msg => { + const message = JSON.parse(msg.body); + if (message.type === 'RoomPatched') { + this.room.questionsBlocked = message.payload.changes.questionsBlocked; + this.moderationEnabled = message.payload.changes.moderated; + } + }); + }); } } } @@ -150,6 +166,12 @@ export class HeaderComponent implements OnInit { }); } + ngOnDestroy() { + if (this._subscriptionRoomService) { + this._subscriptionRoomService.unsubscribe(); + } + } + showMotdDialog() { this.motdService.requestDialog(); } @@ -305,7 +327,7 @@ export class HeaderComponent implements OnInit { const confirmDialogRef = this.confirmDialog.open(TopicCloudFilterComponent, { autoFocus: false }); - confirmDialogRef.componentInstance.shortId = this.shortId; + confirmDialogRef.componentInstance.target = this.router.url + '/tagcloud'; } public navigateTopicCloudConfig() { @@ -319,11 +341,10 @@ export class HeaderComponent implements OnInit { public blockQuestions() { // flip state if clicked this.room.questionsBlocked = !this.room.questionsBlocked; - this.roomService.updateRoom(this.room).subscribe(r => this.room = r); + this.roomService.updateRoom(this.room).subscribe(); } public startWorkerDialog() { WorkerDialogComponent.addWorkTask(this.dialog, this.room); } - } diff --git a/src/app/components/shared/room-join/room-join.component.ts b/src/app/components/shared/room-join/room-join.component.ts index 66cf2e77a76724c2c9ee0f052e788432d13c520d..cc6a6ea8e23488cf3626cbab9095f34f5a2d9916 100644 --- a/src/app/components/shared/room-join/room-join.component.ts +++ b/src/app/components/shared/room-join/room-join.component.ts @@ -74,7 +74,7 @@ export class RoomJoinComponent implements OnInit { joinRoom(id: string): void { if (!this.sessionCodeFormControl.hasError('required') && !this.sessionCodeFormControl.hasError('minlength')) { if (!this.user) { - this.authenticationService.guestLogin(UserRole.CREATOR).subscribe(() => { + this.authenticationService.guestLogin(UserRole.PARTICIPANT).subscribe(() => { this.getRoom(id); }); } else { @@ -93,6 +93,7 @@ export class RoomJoinComponent implements OnInit { addAndNavigate() { if (this.user.id === this.room.ownerId) { + this.authenticationService.setAccess(this.room.shortId, UserRole.CREATOR); this.router.navigate([`/creator/room/${this.room.shortId}/comments`]); } else { this.roomService.addToHistory(this.room.id); diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html index c79c3e2823960818fdf21898ae076a1f2ad97e60..77632d2beca37497f11686c5500481ebd61944b5 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html +++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html @@ -42,7 +42,7 @@ </p> </span> </div> - <div class="replacementContainer" *ngIf="checkLanguage && user && user.role >= 1"> + <div class="replacementContainer" *ngIf="user && user.role >= 1"> <mat-form-field> <mat-label>{{'tag-cloud-popup.tag-correction-placeholder' | translate}}</mat-label> <input type="text" diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts index 0c25bfda2a286c6c3f77110629964be649708243..0ad09a4ccbc6945c625d236e41cbc45e3138b552 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts @@ -10,6 +10,7 @@ import { TSMap } from 'typescript-map'; import { CommentService } from '../../../../services/http/comment.service'; import { NotificationService } from '../../../../services/util/notification.service'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { UserRole } from '../../../../models/user-roles.enum'; const CLOSE_TIME = 1500; @@ -30,7 +31,6 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { user: User; selectedLang: Language = 'en-US'; spellingData: string[] = []; - checkLanguage = false; private _popupHoverTimer: number; private _popupCloseTimer: number; private _hasLeft = true; @@ -77,10 +77,9 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { this.close(); } - enter(elem: HTMLElement, tag: string, tagData: TagCloudDataTagEntry, hoverDelayInMs: number, checkLanguage: boolean): void { - this.checkLanguage = checkLanguage; - if (checkLanguage) { - this.spellingData = []; + enter(elem: HTMLElement, tag: string, tagData: TagCloudDataTagEntry, hoverDelayInMs: number): void { + this.spellingData = []; + if (this.user && this.user.role > UserRole.PARTICIPANT) { this.languagetoolService.checkSpellings(tag, 'auto').subscribe(correction => { const langKey = correction.language.code.split('-')[0].toUpperCase(); if (['DE', 'FR', 'EN'].indexOf(langKey) < 0) { 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 bab62dd88ddd19b532fa6c658533e8be84bf4270..a75007d8a8441e15adf7706319dc7b1224505971 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts @@ -1,4 +1,4 @@ -import { AfterContentInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterContentInit, AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { CloudData, @@ -111,16 +111,16 @@ const getResolvedDefaultColors = (): string[] => { const getDefaultCloudParameters = (): CloudParameters => { const resDefaultColors = getResolvedDefaultColors(); const weightSettings: CloudWeightSettings = [ - {maxVisibleElements: -1, color: resDefaultColors[1], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[2], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[3], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[4], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[5], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[6], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[7], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[8], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[9], rotation: 0}, - {maxVisibleElements: -1, color: resDefaultColors[10], rotation: 0}, + {maxVisibleElements: 5, color: resDefaultColors[1], rotation: 0}, + {maxVisibleElements: 6, color: resDefaultColors[2], rotation: 0}, + {maxVisibleElements: 5, color: resDefaultColors[3], rotation: 0}, + {maxVisibleElements: 6, color: resDefaultColors[4], rotation: 0}, + {maxVisibleElements: 5, color: resDefaultColors[5], rotation: 0}, + {maxVisibleElements: 6, color: resDefaultColors[6], rotation: 0}, + {maxVisibleElements: 5, color: resDefaultColors[7], rotation: 0}, + {maxVisibleElements: 6, color: resDefaultColors[8], rotation: 0}, + {maxVisibleElements: 5, color: resDefaultColors[9], rotation: 0}, + {maxVisibleElements: 6, color: resDefaultColors[10], rotation: 0}, ]; return { fontFamily: 'Dancing Script', @@ -136,7 +136,6 @@ const getDefaultCloudParameters = (): CloudParameters => { hoverDelay: 0.4, delayWord: 0, randomAngles: true, - checkSpelling: true, sortAlphabetically: false, textTransform: CloudTextStyle.normal, cloudWeightSettings: weightSettings @@ -148,7 +147,7 @@ const getDefaultCloudParameters = (): CloudParameters => { templateUrl: './tag-cloud.component.html', styleUrls: ['./tag-cloud.component.scss'] }) -export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { +export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, AfterViewInit { @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent; @ViewChild(TagCloudPopUpComponent) popup: TagCloudPopUpComponent; @@ -228,7 +227,6 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { 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%', @@ -253,6 +251,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { this.userRole = this.route.snapshot.data.roles[0]; this.route.params.subscribe(params => { this.shortId = params['shortId']; + this.authenticationService.checkAccess(this.shortId); this.authenticationService.guestLogin(UserRole.PARTICIPANT).subscribe(r => { this.roomService.getRoomByShortId(this.shortId).subscribe(room => { this.room = room; @@ -285,6 +284,10 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { this.setCloudParameters(TagCloudComponent.getCurrentCloudParameters(), false); } + ngAfterViewInit() { + this.rebuildData(); + } + ngOnDestroy() { document.getElementById('footer_rescale').style.display = 'block'; this.headerInterface.unsubscribe(); @@ -329,6 +332,9 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { } rebuildData() { + if (!this.child || !this.dataManager.currentData) { + return; + } const newElements = []; const data = this.dataManager.currentData; const countFiler = []; @@ -432,8 +438,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { }); elem.addEventListener('mouseenter', () => { this.popup.enter(elem, dataElement.text, dataElement.tagData, - (this._currentSettings.hoverTime + this._currentSettings.hoverDelay) * 1_000, - this._currentSettings.checkSpelling); + (this._currentSettings.hoverTime + this._currentSettings.hoverDelay) * 1_000); }); }); } @@ -470,7 +475,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit { 'font-size: ' + (this._currentSettings.fontSizeMin + fontRange * i).toFixed(0) + '%; }'); } customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover, .spacyTagCloud > span:hover > a { ' + - 'color: ' + this._currentSettings.fontColor + '; ' + + 'color: ' + this._currentSettings.fontColor + ' !important; ' + 'background-color: ' + this._currentSettings.backgroundColor + '; }'); customTagCloudStyles.sheet.insertRule('.spacyTagCloudContainer { ' + 'background-color: ' + this._currentSettings.backgroundColor + '; }'); diff --git a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts index b6f0edf187bf9b80fc536327d88e54c876e8b7c5..240eb3b0b6eade4a3b802b0b482c091fec322e10 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts @@ -94,10 +94,6 @@ export interface CloudParameters { * Sorts the cloud alphabetical. */ sortAlphabetically: boolean; - /** - * Checks if the word is spelled correctly, if not, do not display it. - */ - checkSpelling: boolean; /** * Custom CSS text transform setting */ diff --git a/src/app/models/comment.ts b/src/app/models/comment.ts index 31f2711c1bb7d74934ca8e55802fd4aee2fdca20..8de26629ffa7d82ce9c47e34634dbe0b82b87de7 100644 --- a/src/app/models/comment.ts +++ b/src/app/models/comment.ts @@ -1,4 +1,4 @@ -import { dashCaseToCamelCase } from '@angular/compiler/src/util'; +import { Model } from '../services/http/spacy.service'; import { CorrectWrong } from './correct-wrong.enum'; export class Comment { @@ -24,6 +24,7 @@ export class Comment { keywordsFromSpacy: string[]; upvotes: number; downvotes: number; + language: Language; constructor(roomId: string = '', creatorId: string = '', @@ -43,7 +44,8 @@ export class Comment { keywordsFromQuestioner: string[] = [], keywordsFromSpacy: string[] = [], upvotes = 0, - downvotes = 0) { + downvotes = 0, + language = Language.auto) { this.id = ''; this.roomId = roomId; this.creatorId = creatorId; @@ -65,5 +67,22 @@ export class Comment { this.keywordsFromSpacy = keywordsFromSpacy; this.upvotes = upvotes; this.downvotes = downvotes; + this.language = language; + } + + static mapModelToLanguage(model: Model): Language { + return Language[model] || Language.auto; } } + +export enum Language { + de = 'DE', + en = 'EN', + fr = 'FR', + es = 'ES', + it = 'IT', + nl = 'NL', + pt = 'PT', + auto = 'AUTO' +} + diff --git a/src/app/models/room.ts b/src/app/models/room.ts index 6fff1025597c7b25ecf6da819201dc982f863d8d..abd4a42965183ac5dcf0adc0381067e857996cd1 100644 --- a/src/app/models/room.ts +++ b/src/app/models/room.ts @@ -1,5 +1,3 @@ -import { TSMap } from 'typescript-map'; - export class Room { id: string; revision: string; @@ -15,7 +13,7 @@ export class Room { threshold: number; tags: string[]; questionsBlocked: boolean; - profanityFilter: boolean; + profanityFilter: ProfanityFilter; constructor( ownerId: string = '', @@ -30,7 +28,7 @@ export class Room { threshold: number = null, tags: string[] = [], questionsBlocked: boolean = false, - profanityFilter: boolean = true + profanityFilter: ProfanityFilter = ProfanityFilter.none ) { this.id = ''; this.ownerId = ownerId; @@ -48,3 +46,11 @@ export class Room { this.profanityFilter = profanityFilter; } } + +export enum ProfanityFilter { + all = 'ALL', + languageSpecific = 'LANGUAGE_SPECIFIC', + partialWords = 'PARTIAL_WORDS', + none = 'NONE', + deactivated = 'DEACTIVATED' +} diff --git a/src/app/services/http/comment.service.ts b/src/app/services/http/comment.service.ts index 835a6ca0e5063f0eb97e07ed581c225a12d21c96..b92ecdd14da0249c69d83df9e6fd5cc067227104 100644 --- a/src/app/services/http/comment.service.ts +++ b/src/app/services/http/comment.service.ts @@ -86,7 +86,8 @@ export class CommentService extends BaseHttpService { roomId: comment.roomId, body: comment.body, read: comment.read, creationTimestamp: comment.timestamp, tag: comment.tag, keywordsFromSpacy: JSON.stringify(comment.keywordsFromSpacy), - keywordsFromQuestioner: JSON.stringify(comment.keywordsFromQuestioner) + keywordsFromQuestioner: JSON.stringify(comment.keywordsFromQuestioner), + language: comment.language }, httpOptions).pipe( tap(_ => ''), catchError(this.handleError<Comment>('addComment')) @@ -152,6 +153,7 @@ export class CommentService extends BaseHttpService { patchComment(comment: Comment, changes: TSMap<string, any>) { const connectionUrl = this.apiUrl.base + this.apiUrl.comment + '/' + comment.id; return this.http.patch(connectionUrl, changes, httpOptions).pipe( + map(c => this.parseComment(c as Comment)), tap(_ => ''), catchError(this.handleError<any>('patchComment')) ); @@ -222,8 +224,10 @@ export class CommentService extends BaseHttpService { parseComment(comment: Comment): Comment { + if (!comment){ + return; + } comment.userNumber = this.hashCode(comment.creatorId); - // make list out of string "array" comment.keywordsFromQuestioner = comment.keywordsFromQuestioner ? JSON.parse(comment.keywordsFromQuestioner as unknown as string) : null; comment.keywordsFromSpacy = comment.keywordsFromSpacy ? JSON.parse(comment.keywordsFromSpacy as unknown as string) : null; diff --git a/src/app/services/http/languagetool.service.ts b/src/app/services/http/languagetool.service.ts index 0305c581109d8a7b1c70634c1919a35ab810854a..5a3fea983bf29a2df95b61759ce242d2f868a177 100644 --- a/src/app/services/http/languagetool.service.ts +++ b/src/app/services/http/languagetool.service.ts @@ -5,7 +5,14 @@ import { catchError } from 'rxjs/operators'; import { Model } from './spacy.service'; import { Observable } from 'rxjs'; -export type Language = 'de-DE' | 'en-US' | 'fr' | 'auto'; +export type Language = 'de' | 'de-AT' | 'de-CH' | 'de-DE' | + 'en' | 'en-AU' | 'en-CA' | 'en-GB' | 'en-US' | + 'fr' | + 'es' | + 'it' | + 'nl' | 'nl-BE' | + 'pt' | 'pt-BR' | 'pt-PT' | + 'auto'; export interface LanguagetoolResult { software: { @@ -75,17 +82,40 @@ export class LanguagetoolService extends BaseHttpService { mapLanguageToSpacyModel(language: Language): Model { switch (language) { + case 'de': + case 'de-AT': + case 'de-CH': case 'de-DE': return 'de'; + case 'en': + case 'en-AU': + case 'en-CA': + case 'en-GB': case 'en-US': return 'en'; + case 'es': + return 'es'; case 'fr': - return 'fr'; + return 'fr'; + case 'it': + return 'it'; + case 'nl': + case 'nl-BE': + return 'nl'; + case 'pt': + case 'pt-BR': + case 'pt-PT': + return 'pt'; default: return 'auto'; } } + isSupportedLanguage(language: Language) { + const supportedLanguages: Model[] = ['de', 'en', 'fr']; + return supportedLanguages.includes(this.mapLanguageToSpacyModel(language)); + } + checkSpellings(text: string, language: Language): Observable<LanguagetoolResult> { const url = '/languagetool'; return this.http.get<LanguagetoolResult>(url, { diff --git a/src/app/services/http/room.service.ts b/src/app/services/http/room.service.ts index 9dcf954d9f142b1a368ce86090c362a625386d56..d6069923f54f827d312ab00661298510fea2caaa 100644 --- a/src/app/services/http/room.service.ts +++ b/src/app/services/http/room.service.ts @@ -1,15 +1,12 @@ import { Injectable } from '@angular/core'; import { Room } from '../../models/room'; -import { RoomJoined } from '../../models/events/room-joined'; -import { RoomCreated } from '../../models/events/room-created'; import { UserRole } from '../../models/user-roles.enum'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { catchError, tap, map } from 'rxjs/operators'; +import { catchError, tap } from 'rxjs/operators'; import { AuthenticationService } from './authentication.service'; import { BaseHttpService } from './base-http.service'; import { EventService } from '../util/event.service'; -import { TSMap } from 'typescript-map'; const httpOptions = { headers: new HttpHeaders({}) @@ -110,7 +107,7 @@ export class RoomService extends BaseHttpService { addToHistory(roomId: string): void { const connectionUrl = `${ this.apiUrl.base + this.apiUrl.user }/${ this.authService.getUser().id }/roomHistory`; - this.http.post(connectionUrl, { roomId: roomId, lastVisit: this.joinDate.getTime() }, httpOptions).subscribe(() => {}); + this.http.post(connectionUrl, { roomId, lastVisit: this.joinDate.getTime() }, httpOptions).subscribe(); } removeFromHistory(roomId: string): Observable<Room> { @@ -138,9 +135,6 @@ export class RoomService extends BaseHttpService { } setRoomId(room: Room): void { - // temp solution until the backend is updated - room.profanityFilter = localStorage.getItem('room-profanity-filter') !== 'false' || - !localStorage.getItem('room-profanity-filter') ? true : false; localStorage.setItem('roomId', room.id); } } diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts index c7a52edb33ffe593c6578021ba112e54e47f3946..8314763856c1437fdbd67da408c87daa27a81d99 100644 --- a/src/app/services/util/room-data.service.ts +++ b/src/app/services/util/room-data.service.ts @@ -7,6 +7,8 @@ import { CommentService } from '../http/comment.service'; import { CorrectWrong } from '../../models/correct-wrong.enum'; import { RoomService } from '../http/room.service'; import { TopicCloudAdminService } from './topic-cloud-admin.service'; +import { ProfanityFilter, Room } from '../../models/room'; +import { WsRoomService } from '../websockets/ws-room.service'; export interface UpdateInformation { type: 'CommentCreated' | 'CommentPatched' | 'CommentHighlighted' | 'CommentDeleted'; @@ -92,11 +94,13 @@ export class RoomDataService { private _currentRoomId: string = null; private _savedCommentsBeforeFilter = new Map(); private _savedCommentsAfterFilter = new Map(); + private room: Room; constructor(private wsCommentService: WsCommentService, private commentService: CommentService, private roomService: RoomService, - private topicCloudAdminService: TopicCloudAdminService) { + private topicCloudAdminService: TopicCloudAdminService, + private wsRoomService: WsRoomService) { } get currentRoomData() { @@ -115,7 +119,6 @@ export class RoomDataService { getRoomData(roomId: string, freezed: boolean = false): Observable<Comment[]> { if (roomId && roomId === this._currentRoomId) { - this.checkProfanity(); return of(freezed ? [...this._currentComments] : this._currentComments); } const tempSubject = new Subject<Comment[]>(); @@ -127,23 +130,50 @@ export class RoomDataService { return tempSubject.asObservable(); } - private checkProfanity() { - this.roomService.getRoom(localStorage.getItem('roomId')).subscribe(room => { - if (room.profanityFilter) { - this._currentComments.forEach(comment => { - comment.body = this._savedCommentsAfterFilter.get(comment.id); - }); + public checkProfanity(comment: Comment) { + const finish = new Subject<boolean>(); + const subscription = finish.asObservable().subscribe(_ => { + if (this.room.profanityFilter !== ProfanityFilter.deactivated) { + comment.body = this._savedCommentsAfterFilter.get(comment.id); } else { - this._currentComments.forEach(comment => { - comment.body = this._savedCommentsBeforeFilter.get(comment.id); - }); + comment.body = this._savedCommentsBeforeFilter.get(comment.id); } + subscription.unsubscribe(); }); + + if (!this._savedCommentsAfterFilter.get(comment.id) || !this.room) { + if (!this.room) { + this.roomService.getRoom(localStorage.getItem('roomId')).subscribe(room => { + this.room = room; + this.setCommentBody(comment); + finish.next(true); + }); + } else { + this.setCommentBody(comment); + finish.next(true); + } + } else { + finish.next(true); + } } - private setCommentBodies(comment: Comment) { + private setCommentBody(comment: Comment) { this._savedCommentsBeforeFilter.set(comment.id, comment.body); - this._savedCommentsAfterFilter.set(comment.id, this.topicCloudAdminService.filterProfanityWords(comment.body)); + this._savedCommentsAfterFilter.set(comment.id, this.filterCommentOfProfanity(this.room, comment)); + } + + private filterAllCommentsBodies() { + this._currentComments.forEach(comment => { + comment.body = this._savedCommentsBeforeFilter.get(comment.id); + this.setCommentBody(comment); + this.checkProfanity(comment); + }); + } + + private filterCommentOfProfanity(room: Room, comment: Comment): string { + const partialWords = room.profanityFilter === ProfanityFilter.all || room.profanityFilter === ProfanityFilter.partialWords; + const languageSpecific = room.profanityFilter === ProfanityFilter.all || room.profanityFilter === ProfanityFilter.languageSpecific; + return this.topicCloudAdminService.filterProfanityWords(comment.body, partialWords, languageSpecific, comment.language); } private removeCommentBodies(key: string) { @@ -162,22 +192,29 @@ export class RoomDataService { if (this._wsCommentServiceSubscription) { this._wsCommentServiceSubscription.unsubscribe(); } - this._wsCommentServiceSubscription = this.wsCommentService.getCommentStream(roomId) - .subscribe(msg => this.onMessageReceive(msg)); - this.commentService.getAckComments(roomId).subscribe(comments => { - this._currentComments = comments; - for (const comment of comments) { - this.setCommentBodies(comment); - this._fastCommentAccess[comment.id] = comment; + this.roomService.getRoom(roomId).subscribe(room => { + this.room = room; + this._wsCommentServiceSubscription = this.wsCommentService.getCommentStream(roomId) + .subscribe(msg => this.onMessageReceive(msg)); + this.commentService.getAckComments(roomId).subscribe(comments => { + this._currentComments = comments; + for (const comment of comments) { + this.setCommentBody(comment); + this._fastCommentAccess[comment.id] = comment; + } + this.triggerUpdate(UpdateType.force, null); + }); + }); + this.wsRoomService.getRoomStream(roomId).subscribe(msg => { + const message = JSON.parse(msg.body); + if (message.type === 'RoomPatched') { + this.room = message.payload.changes; + this.filterAllCommentsBodies(); } - this.triggerUpdate(UpdateType.force, null); }); } private triggerUpdate(type: UpdateType, additionalInformation: UpdateInformation) { - if ((additionalInformation && additionalInformation.type === 'CommentCreated') || !additionalInformation) { - this.checkProfanity(); - } if (type === UpdateType.force) { this._commentUpdates.next(this._currentComments); } else if (type === UpdateType.commentStream) { @@ -215,19 +252,23 @@ export class RoomDataService { c.tag = payload.tag; c.creatorId = payload.creatorId; c.userNumber = this.commentService.hashCode(c.creatorId); + c.keywordsFromQuestioner = JSON.parse(payload.keywordsFromQuestioner); + this._fastCommentAccess[c.id] = c; + this._currentComments.push(c); this.triggerUpdate(UpdateType.commentStream, { type: 'CommentCreated', finished: false, comment: c }); this.commentService.getComment(c.id).subscribe(comment => { - this._fastCommentAccess[comment.id] = comment; - this._currentComments.push(comment); - this.setCommentBodies(comment); + for (const key of Object.keys(comment)) { + c[key] = comment[key]; + } + this.setCommentBody(c); this.triggerUpdate(UpdateType.commentStream, { type: 'CommentCreated', finished: true, - comment + comment: c }); }); } diff --git a/src/app/services/util/tag-cloud-data.service.ts b/src/app/services/util/tag-cloud-data.service.ts index 176e496094b830ff131ea951bdd97faeb96ac83a..07fb647467c658e3ed2dc3dbec70508a26c8e83a 100644 --- a/src/app/services/util/tag-cloud-data.service.ts +++ b/src/app/services/util/tag-cloud-data.service.ts @@ -272,11 +272,6 @@ export class TagCloudDataService { private fetchData(): void { this._roomDataService.getRoomData(this._roomId).subscribe((comments: Comment[]) => { this._lastFetchedComments = comments; - if (this._isDemoActive) { - this._lastMetaData.commentCount = comments.length; - } else { - this._currentMetaData.commentCount = comments.length; - } this.rebuildTagData(); }); } @@ -300,6 +295,7 @@ export class TagCloudDataService { const data: TagCloudData = new Map<string, TagCloudDataTagEntry>(); const users = new Set<number>(); const filteredComments = this._lastFetchedComments.filter(comment => this._currentFilter.checkComment(comment)); + currentMeta.commentCount = filteredComments.length; for (const comment of filteredComments) { let keywords = comment.keywordsFromQuestioner; if (this._supplyType === TagCloudDataSupplyType.keywordsAndFullText) { diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index bc650ebd272310cc23e670f7429f0cafe3684c16..3652ccb577f00977a3e274877eb0f36ab40e821b 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -46,10 +46,10 @@ export class TopicCloudAdminService { de: this.getDefaultSpacyTagsDE(), en: this.getDefaultSpacyTagsEN() }, - considerVotes: false, + considerVotes: true, profanityFilter: true, blacklistIsActive: true, - keywordORfulltext: KeywordOrFulltext.keyword + keywordORfulltext: KeywordOrFulltext.both }; } return data; @@ -58,7 +58,9 @@ export class TopicCloudAdminService { static getDefaultSpacyTagsDE(): string[] { const tags: string[] = []; spacyLabels.de.forEach(label => { - tags.push(label.tag); + if (label.enabledByDefault) { + tags.push(label.tag); + } }); return tags; } @@ -66,7 +68,9 @@ export class TopicCloudAdminService { static getDefaultSpacyTagsEN(): string[] { const tags: string[] = []; spacyLabels.en.forEach(label => { - tags.push(label.tag); + if (label.enabledByDefault) { + tags.push(label.tag); + } }); return tags; } @@ -85,6 +89,7 @@ export class TopicCloudAdminService { if (_adminData.profanityFilter) { _adminData.blacklist = _adminData.blacklist.concat(this.getProfanityListFromStorage().concat(this.profanityWords)); } + localStorage.setItem(TopicCloudAdminService.adminKey, JSON.stringify(_adminData)); this.adminData.next(_adminData); }); } @@ -179,20 +184,21 @@ export class TopicCloudAdminService { }); } - filterProfanityWords(str: string): string { - let questionWithProfanity = str; - this.profanityWords.concat(this.getProfanityListFromStorage()).map((word) => { - questionWithProfanity = questionWithProfanity - .toLowerCase() - .includes(word) - ? this.replaceString( - questionWithProfanity, - word, - this.generateCensoredWord(word.length) - ) - : questionWithProfanity; + filterProfanityWords(str: string, censorPartialWordsCheck: boolean, censorLanguageSpecificCheck: boolean, lang?: string){ + let filteredString = str; + let profWords = []; + if (censorLanguageSpecificCheck) { + profWords = BadWords[(lang !== 'AUTO' ? lang.toLowerCase() : 'de')]; + } else { + profWords = this.profanityWords; + } + const toCensoredString = censorPartialWordsCheck ? str.toLowerCase() : str.toLowerCase().split(' '); + profWords.concat(this.getProfanityListFromStorage()).forEach(word => { + if (toCensoredString.includes(word)) { + filteredString = this.replaceString(filteredString, word, this.generateCensoredWord(word.length)); + } }); - return questionWithProfanity; + return filteredString; } private replaceString(str: string, search: string, replace: string) { diff --git a/src/app/services/websockets/ws-room.service.spec.ts b/src/app/services/websockets/ws-room.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..558c8827b4c64e95bf7a11943920f1377b16369a --- /dev/null +++ b/src/app/services/websockets/ws-room.service.spec.ts @@ -0,0 +1,16 @@ +/*import { TestBed } from '@angular/core/testing'; + +import { WsRoomService } from './ws-room.service'; + +describe('WsRoomService', () => { + let service: WsRoomService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(WsRoomService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +});*/ diff --git a/src/app/services/websockets/ws-room.service.ts b/src/app/services/websockets/ws-room.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac1cb6817be34200f1806c265a1f441410e97f8f --- /dev/null +++ b/src/app/services/websockets/ws-room.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { WsConnectorService } from './ws-connector.service'; +import { Observable } from 'rxjs'; +import { IMessage } from '@stomp/stompjs'; + +@Injectable({ + providedIn: 'root' +}) +export class WsRoomService { + + constructor(private wsConnector: WsConnectorService) { } + + getRoomStream(roomId: string): Observable<IMessage> { + return this.wsConnector.getWatcher(`/topic/${roomId}.room.stream`); + } +} diff --git a/src/app/utils/create-comment-keywords.ts b/src/app/utils/create-comment-keywords.ts index ab2994e0f537e979de194f186838ac7989838343..1e9d595b5a48fddf1b7f26a077deb39986d4bb3d 100644 --- a/src/app/utils/create-comment-keywords.ts +++ b/src/app/utils/create-comment-keywords.ts @@ -4,7 +4,7 @@ import { map } from 'rxjs/operators'; export class CreateCommentKeywords { static isSpellingAcceptable(languagetoolService: LanguagetoolService, text: string, language: Language = 'auto') { - text = this.cleanUTFEmoji(text); + text = this.cleaningFunction(text); return languagetoolService.checkSpellings(text, language).pipe( map(result => { const wordCount = text.trim().split(' ').length; @@ -19,9 +19,16 @@ export class CreateCommentKeywords { ); } - private static cleanUTFEmoji(text: string): string { + static cleaningFunction(text: string): string { // eslint-disable-next-line max-len - const regex = /(?:\:.*?\:|[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g; - return text.replace(regex, ''); + const regexEmoji = new RegExp('\uD918\uDD28|\ufe0f|\u200D|\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]', 'g'); + const regexKatex = new RegExp('\\$[^$\\n ]+\\$|\\$\\$[^$\\n ]+\\$\\$','g'); + const regexMarkdown = new RegExp('(?:__|[*#])|\\[(.+?)]\\((.+?)\\)', 'g'); + const regexBlank = new RegExp('[\\s]{2,}', 'gu'); + text = text.replace(regexKatex,''); + text = text.replace(regexEmoji,''); + text = text.replace(regexMarkdown,''); + text = text.replace(regexBlank, ' '); + return text; } } diff --git a/src/app/utils/filter-options.ts b/src/app/utils/filter-options.ts index 7669a7d697a59d6d442d135815612e893562e45a..9268cbcf7cdb9cfab1ba03bc72c50e772f977386 100644 --- a/src/app/utils/filter-options.ts +++ b/src/app/utils/filter-options.ts @@ -26,12 +26,13 @@ export class CommentFilter { filterSelected = ''; keywordSelected = ''; tagSelected = ''; + userNumberSelected = 0; paused = false; timeStampUntil = 0; periodSet: Period = Period.twoWeeks; - timeStampNow = 0; + timeStampNow = 0; constructor(obj?: any) { if (obj) { @@ -42,6 +43,7 @@ export class CommentFilter { this.timeStampUntil = obj.timeStampUntil; this.periodSet = obj.periodSet; this.timeStampNow = obj.timeStampNow; + this.userNumberSelected = obj.userNumberSelected; } } @@ -56,6 +58,7 @@ export class CommentFilter { public static generateFilterNow(filterSelected: string): CommentFilter { const filter = new CommentFilter(); + filter.userNumberSelected = 0; filter.filterSelected = filterSelected; filter.paused = false; @@ -72,6 +75,8 @@ export class CommentFilter { tagSelected: string, keywordSelected: string): CommentFilter { const filter = new CommentFilter(); + + filter.userNumberSelected = 0; filter.filterSelected = filterSelected; filter.paused = true; @@ -154,6 +159,10 @@ export class CommentFilter { } } + if (this.userNumberSelected !== 0) { + return com.userNumber === this.userNumberSelected; + } + if (this.keywordSelected !== '') { return com.keywordsFromQuestioner.includes(this.keywordSelected); } diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index 801beca059362d3d9a25445d6676b8cea19be405..2002c269489ebc3c0230471daf0ce991b499fc13 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -333,7 +333,11 @@ "token-deleted": "Der Token wurde gelöscht.", "tokens-deleted": "Alle Tokens dieser Sitzung wurden gelöscht.", "profanity-filter": "Schimpfwörter zensieren", - "words-will-be-overwritten": "profane words will be overwritten with '***'" + "language-specific-filter": "Sprachspezifisch filtern", + "partial-words-filter": "Teilwörter filtern", + "words-will-be-overwritten": "Profane Wörter werden mit '***' überschrieben", + "only-specific-language-will-be-filtered": "Nur die Sprache der Frage wird gefiltert", + "partial-words-will-be-filtered": "Profane Teilwörter werden auch gefiltert" }, "session": { "a11y-description": "Gib eine Beschreibung für die Sitzung ein.", @@ -343,6 +347,70 @@ "session-name": "Name der Sitzung", "preview": "Vorschau" }, + "tag-cloud-popup": { + "few-seconds": "wenige Sekunden", + "few-minutes": "wenige Minuten", + "some-minutes": "{{minutes}} Minuten", + "one-hour": "1 Stunde", + "some-hours": "{{hours}} Stunden", + "one-day": "1 Tag", + "some-days": "{{days}} Tage", + "one-week": "1 Woche", + "some-weeks": "{{weeks}} Wochen", + "some-months": "{{months}} Monate", + "tag-correction-placeholder": "Korrektur" + }, + "topic-cloud-dialog": { + "cancel": "Abbrechen", + "save": "Speichern", + "edit": "Bearbeiten", + "delete": "Löschen", + "question-count-singular": "Frage", + "question-count-plural": "Fragen", + "edit-keyword-tip": "Neues Thema", + "no-keywords-note": "Es gibt keine Themen", + "consider-votes": "Votes berücksichtigen", + "profanity": "Schimpfwörter zensieren", + "hide-blacklist-words": "Blacklist Stichworte verbergen", + "sort-alpha": "Alphabetisch", + "sort-count": "Fragenanzahl", + "sort-vote": "Votes", + "keyword-search": "Stichwort suchen", + "edit-profanity-list": "Zensurliste bearbeiten", + "edit-blacklist-list": "Blackliste bearbeiten", + "add-word": "Wort hinzufügen", + "enter-word": "Wort eingeben", + "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", + "keyword-delete": "Stichwort gelöscht", + "keyword-edit": "Stichwort umbenannt", + "keywords-merge": "Stichwörter zusammengefügt", + "changes-gone-wrong": "Etwas ist schiefgelaufen", + "english": "Englisch", + "german": "Deutsch", + "select-all": "Alle auswählen", + "keyword-counter": "Anzahl der Themen", + "sort": "Sortieren", + "words-will-be-overwritten": "unanständige Wörter werden mit '***' überschrieben", + "keyword-from-spacy": "Stichwort von spaCy", + "keyword-from-questioner": "Stichwort vom Fragesteller" + }, + "tag-cloud": { + "demo-data-topic": "Thema %d", + "overview-question-topic-tooltip": "Anzahl gestellter Fragen mit diesem Thema", + "overview-questioners-topic-tooltip": "Anzahl Fragensteller*innen mit diesem Thema", + "period-since-first-comment":"Zeitraum seit erstem Kommentar", + "upvote-topic": "Upvotes für dieses Thema", + "downvote-topic": "Downvotes für dieses Thema", + "blacklist-topic": "Thema zur Blacklist hinzufügen" + }, "tag-cloud-config":{ "general":"Allgemein", "overflow":"Überlauf", diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index 9093cab6536a3a8c5d51fd546de29ce5843724db..af74d8abae3339883382be940cc9d6ff2943396e 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -334,7 +334,11 @@ "token-deleted": "Token has been deleted.", "tokens-deleted": "All tokens of this sessions have been deleted.", "profanity-filter": "Censor profanity", - "words-will-be-overwritten": "profane words will be overwritten with '***'" + "language-specific-filter": "Filter language-specific", + "partial-words-filter": "Filter partial words", + "words-will-be-overwritten": "Profane words will be overwritten with '***'", + "only-specific-language-will-be-filtered": "Only the language of the question will be filtered", + "partial-words-will-be-filtered": "Profane partial words will be also filtered" }, "session": { "a11y-description": "Enter a description of the session", @@ -344,6 +348,70 @@ "session-name": "Session name", "preview": "Preview" }, + "tag-cloud": { + "demo-data-topic": "Topic %d", + "overview-question-topic-tooltip": "Number of questions with this topic", + "overview-questioners-topic-tooltip": "Number of questioners with this topic", + "period-since-first-comment":"Period since first comment", + "upvote-topic": "Upvotes for this topic", + "downvote-topic": "Downvotes for this topic", + "blacklist-topic": "Add topic to blacklist" + }, + "tag-cloud-popup": { + "few-seconds": "few seconds", + "few-minutes": "few minutes", + "some-minutes": "{{minutes}} minutes", + "one-hour": "1 hour", + "some-hours": "{{hours}} hours", + "one-day": "1 day", + "some-days": "{{days}} days", + "one-week": "1 week", + "some-weeks": "{{weeks}} weeks", + "some-months": "{{months}} months", + "tag-correction-placeholder": "Correction" + }, + "topic-cloud-dialog":{ + "edit": "Edit", + "delete": "Delete", + "cancel": "Cancel", + "save": "Save", + "question-count-singular": "Question", + "question-count-plural": "Questions", + "edit-keyword-tip": "New topic", + "no-keywords-note": "There are no topics!", + "consider-votes": "Consider Votes", + "profanity": "Censor profanity", + "hide-blacklist-words": "Hide blacklist keywords", + "sort-alpha": "Alphabetically", + "sort-count": "Questions count", + "sort-vote": "Votes", + "keyword-search": "Search keyword", + "edit-profanity-list": "Edit profanity list", + "edit-blacklist-list": "Edit blacklist list", + "add-word": "Add Word", + "enter-word": "Enter word", + "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", + "keyword-delete": "keyword deleted", + "keyword-edit": "keyword renamed", + "keywords-merge": "keywords merged", + "changes-gone-wrong": "somthing has gone wrong", + "english": "English", + "german": "German", + "select-all": "Select all", + "keyword-counter": "Topic count", + "sort": "Sort", + "words-will-be-overwritten": "profane words will be overwritten with '***'", + "keyword-from-spacy": "Keyword from spaCy", + "keyword-from-questioner": "Keyword from questioner" + }, "tag-cloud-config":{ "general":"General", "overflow":"Overflow", diff --git a/src/assets/i18n/home/de.json b/src/assets/i18n/home/de.json index d782b388faf7b8f76627c0e577b6790301aaae44..2d60f5976339e0bc05d73ce1818cc54b90bc4bb3 100644 --- a/src/assets/i18n/home/de.json +++ b/src/assets/i18n/home/de.json @@ -50,9 +50,9 @@ "cancel": "Abbrechen", "continue": "Weiter", "reset": "Zurücksetzen", - "continue-with-all-questions" : "Weiter mit kompletter Fragenliste", - "continue-with-current-questions": "Weiter mit aktuellen Filtern", - "continue-with-all-questions-from-now": "Weiter mit allen Fragen ab jetzt" + "continue-with-all-questions" : "Weiter mit allen Fragen der Sitzung", + "continue-with-current-questions": "Weiter mit aktueller Fragenliste", + "continue-with-all-questions-from-now": "Weiter mit Fragen, die ab jetzt gestellt werden" }, "header": { "abort": "Abbrechen", @@ -70,6 +70,7 @@ "login": "Anmelden", "logout": "Abmelden", "block": "Fragen sperren", + "unlock": "Fragen entsperen", "moderation-enabled": "Die Sitzung wird moderiert.", "QR": "QR-Code der Sitzung in Vollansicht", "my-account": "Optionen", @@ -320,6 +321,10 @@ "remove-successful": "Wort entfernt" }, "worker-dialog": { - "running": "Laufend" + "running": "Laufend", + "room-name" : "Sitzungsname", + "comments" : "Abgearbeitete Fragen", + "bad-spelled" : "Zu schlechte Rechtschreibung", + "failed" : "Fehler aufgetreten" } } diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json index 32e8901a62828fbf6c031af10f45fced05a79211..c55219181bb5f52aaf58cfb7e80eba8cb9745fec 100644 --- a/src/assets/i18n/home/en.json +++ b/src/assets/i18n/home/en.json @@ -56,6 +56,7 @@ "cancel": "Cancel", "delete": "Delete", "block" : "Block questions", + "unlock": "Unlock questions", "delete-account": "Delete account", "home-header": "Collect and rate questions", "id": "Key", @@ -130,9 +131,9 @@ "cancel": "Cancel", "continue": "Continue", "reset": "Reset", - "continue-with-all-questions" : "Continue with complete list of questions", - "continue-with-current-questions": "Continue with current filters", - "continue-with-all-questions-from-now": "Continue with all questions from now" + "continue-with-all-questions" : "Continue with all questions of the session", + "continue-with-current-questions": "Continue with current question list", + "continue-with-all-questions-from-now": "Continue with questions that will be asked form now on" }, "imprint": { @@ -324,6 +325,10 @@ "remove-successful": "Word removed" }, "worker-dialog": { - "running": "running" + "running": "Running", + "room-name" : "Session name", + "comments" : "Questions", + "bad-spelled" : "Spelling too bad", + "failed" : "Error occured" } } diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 9c2bb83c7765f125058880ef5cd96d9f0473cae6..1935f49aa27566449e936f8307cf797ac6998bdc 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -100,7 +100,8 @@ "edit-keyword-hint": "Stichwort editieren", "editing-done-hint": "Editierung abschliessen", "force-language-selection": "Automatische Spracherkennung unpräzise, bitte gewählte Sprache prüfen!", - "add-manually": "Geben Sie bitte die Stichwörter unten mit separatem Komma ein" + "add-manually": "Geben Sie bitte die Stichwörter unten mit separatem Komma ein", + "select-keywords": "Wählen Sie die Stichwörter für Ihre Frage aus" }, "comment-page": { "a11y-comment_input": "Gib deine Frage ein", @@ -232,8 +233,6 @@ "questions-blocked": "Fragen sind deaktiviert!" }, "tag-cloud": { - "config": "Wolkenansicht ändern", - "administration": "Wolkenthemen editieren", "demo-data-topic": "Thema %d", "overview-question-topic-tooltip": "Anzahl gestellter Fragen mit diesem Thema", "overview-questioners-topic-tooltip": "Anzahl Fragensteller*innen mit diesem Thema", @@ -293,7 +292,9 @@ "select-all": "Alle auswählen", "keyword-counter": "Anzahl der Themen", "sort": "Sortieren", - "words-will-be-overwritten": "unanständige Wörter werden mit '***' überschrieben" + "words-will-be-overwritten": "unanständige Wörter werden mit '***' überschrieben", + "keyword-from-spacy": "Stichwort von spaCy", + "keyword-from-questioner": "Stichwort vom Fragesteller" }, "topic-cloud-confirm-dialog": { "cancel": "Abbrechen", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 0d6ae27c2ed6414e5a9caa7a3d54e5c2481440c6..5fc4e2b3f87ed4c16cf29bc271dc31073ae14845 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -110,7 +110,8 @@ "edit-keyword-hint": "Edit keyword", "editing-done-hint": "Finish editing", "force-language-selection": "Language detection inaccurate, please check language settings!", - "add-manually": "You can manually enter the keywords separated with a comma " + "add-manually": "You can manually enter the keywords separated with a comma", + "select-keywords": "Choose the keywords for your question" }, "comment-page": { "a11y-comment_input": "Enter your question", @@ -238,8 +239,6 @@ "questions-blocked": "No further questions!" }, "tag-cloud": { - "config": "Modify cloud view", - "administration": "Edit cloud topics", "demo-data-topic": "Topic %d", "overview-question-topic-tooltip": "Number of questions with this topic", "overview-questioners-topic-tooltip": "Number of questioners with this topic", @@ -299,7 +298,9 @@ "select-all": "Select all", "keyword-counter": "Topic count", "sort": "Sort", - "words-will-be-overwritten": "profane words will be overwritten with '***'" + "words-will-be-overwritten": "profane words will be overwritten with '***'", + "keyword-from-spacy": "Keyword from spaCy", + "keyword-from-questioner": "Keyword from questioner" }, "topic-cloud-confirm-dialog":{ "cancel": "Cancel",