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 63389ced507071094e3a9eb437edbab8aedd3afd..c64c128017cfe15ccd475c5bb795bae290a6ec97 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 @@ -2,14 +2,14 @@ import { AfterContentInit, Component, Inject, OnInit, ViewChild } from '@angular 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 { SpacyService, Model, SpacyKeyword } from '../../../../services/http/spacy.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; + dep: string[]; completed: boolean; editing: boolean; selected: boolean; @@ -28,7 +28,7 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { commentLang: Model; commentBodyChecked: string; keywords: Keyword[] = []; - keywordsOriginal: Keyword[] = []; + keywordsOriginal: SpacyKeyword[] = []; hasKeywordsFromSpacy = false; isLoading = false; langSupported: boolean; @@ -64,8 +64,11 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { buildCreateCommentActionCallback() { return () => { - this.comment.keywordsFromQuestioner = this.keywords.filter(kw => kw.selected && kw.word.length).map(kw => kw.word); - this.comment.keywordsFromSpacy = this.keywordsOriginal.filter(kw => kw.word.length).map(kw => kw.word); + this.comment.keywordsFromQuestioner = this.keywords.filter(kw => kw.selected && kw.word.length).map(kw => ({ + lemma: kw.word, + dep: kw.dep + } as SpacyKeyword)); + this.comment.keywordsFromSpacy = this.keywordsOriginal; this.dialogRef.close(this.comment); }; } @@ -75,24 +78,17 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { // N at first pos = all Nouns(NN de/en) including singular(NN, NNP en), plural (NNPS, NNS en), proper Noun(NNE, NE de) this.spacyService.getKeywords(this.commentBodyChecked, model) - .pipe( - map(keywords => keywords.map(keyword => ({ - word: keyword, + .subscribe(words => { + this.keywordsOriginal = words; + this.keywords = words.map(keyword => ({ + word: keyword.lemma, + dep: [...keyword.dep], completed: false, editing: false, selected: false - } as Keyword))) - ) - .subscribe(words => { - this.keywords = words; + } as Keyword)); this.keywords.sort((a, b) => a.word.localeCompare(b.word)); this.hasKeywordsFromSpacy = this.keywords.length > 0; - - //deep copy - this.keywordsOriginal = [...words]; - for (let i = 0; i < this.keywordsOriginal.length; i++) { - this.keywordsOriginal[i] = {...this.keywordsOriginal[i]}; - } }, () => { this.keywords = []; this.keywordsOriginal = []; @@ -143,6 +139,7 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { this.keywords = tempKeywords.split(',').map((keyword) => ( { word: keyword, + dep: ['ROOT'], completed: true, editing: false, selected: true 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 6fac351f63babc38d63365239df7d20d5bbdcc5a..26570bb7ce2df2392043a9fd69dafb73a221ce30 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 @@ -307,6 +307,11 @@ <mat-icon>close</mat-icon> </button> </mat-form-field> + <button mat-button class="themeRequirementInput reset" + [disabled]="!isTopicRequirementActive()" + (click)="minQuestioners='1'; minQuestioners='1'; minUpvotes='0'; startDate=''; endDate=''"> + {{'topic-cloud-dialog.topic-requirement-reset' | translate}} + </button> </mat-expansion-panel> </mat-accordion> @@ -333,8 +338,7 @@ matTooltip="{{'topic-cloud-dialog.keyword-counter' | translate}}"></mat-icon> <p [ngClass]="{'animation-blink': searchMode}" matTooltip="{{'topic-cloud-dialog.keyword-counter' | translate}}"> - {{searchMode ? filteredKeywords.length : - keywords.length}}</p> + {{searchMode ? filteredKeywords.length : keywords.size}}</p> </div> <div class="margin-left vertical-center"> <button [ngClass]="{'animation-blink': sortMode!=='alphabetic'}" mat-icon-button [matMenuTriggerFor]="sortMenu"> @@ -362,17 +366,18 @@ </mat-menu> <mat-card class="color-surface" - *ngIf="(keywords.length === 0 || (searchMode && filteredKeywords.length === 0))&&!isLoading"> + *ngIf="(keywords.size === 0 || (searchMode && filteredKeywords.length === 0))&&!isLoading"> <p class="color-on-surface" fxLayoutAlign="center"> {{'topic-cloud-dialog.no-keywords-note' | translate}} </p> </mat-card> <mat-accordion> - <div *ngFor="let keyword of (searchMode ? filteredKeywords : keywords); let i = index"> + <div *ngFor="let keyword of (searchMode ? filteredKeywords : getValues()); let i = index"> <mat-expansion-panel class="color-surface" (opened)="panelOpenState = true" - (closed)="panelOpenState = edit = false" [attr.data-index]="i" *ngIf="(blacklistIncludesKeyword(keyword.keyword) && isCreatorOrMod) || !blacklistIncludesKeyword(keyword.keyword)" - matTooltip="{{blacklistIncludesKeyword(keyword.keyword)?'Blacklist: ':''}}{{'topic-cloud-dialog.'+(keyword.keywordType === 2?'Keyword-from-both':(keyword.keywordType === 0?'keyword-from-spacy':'keyword-from-questioner')) | translate}}"> + (closed)="panelOpenState = edit = false" [attr.data-index]="i" + *ngIf="(blacklistIncludesKeyword(keyword.keyword) && isCreatorOrMod) || !blacklistIncludesKeyword(keyword.keyword)" + matTooltip="{{blacklistIncludesKeyword(keyword.keyword)?'Blacklist: ':''}}{{'topic-cloud-dialog.'+(keyword.keywordType === 2?'Keyword-from-both':(keyword.keywordType === 0?'keyword-from-spacy':'keyword-from-questioner')) | translate}}"> <mat-expansion-panel-header class="color-surface"> <mat-panel-title [ngClass]="{'red': blacklistIncludesKeyword(keyword.keyword)}"> {{profanityFilter ? keyword.keywordWithoutProfanity : keyword.keyword}} @@ -384,8 +389,10 @@ </mat-expansion-panel-header> <div *ngFor="let question of keyword.comments"> <mat-divider></mat-divider> - <app-topic-dialog-comment [question]="question.body" [keyword]="keyword.keyword" [maxShowedCharachters]="140" - [profanityFilter]="profanityFilter" [languageSpecific]="censorLanguageSpecificCheck" + <app-topic-dialog-comment [question]="question.body" [keyword]="keyword.keyword" + [maxShowedCharachters]="140" + [profanityFilter]="profanityFilter" + [languageSpecific]="censorLanguageSpecificCheck" [partialWords]="censorPartialWordsCheck" [language]="question.language"></app-topic-dialog-comment> </div> @@ -415,8 +422,10 @@ <button mat-raised-button class="redBackground margin-right" (click)="cancelEdit()">{{'topic-cloud-dialog.cancel' | translate}}</button> <button mat-raised-button class="primaryBackground" - (click)="confirmEdit(keyword)">{{'topic-cloud-dialog.save' - | translate}}</button> + [disabled]="!newKeyword || newKeyword.toLowerCase() === keyword.keyword.toLowerCase()" + (click)="confirmEdit(keyword)"> + {{'topic-cloud-dialog.save' | translate}} + </button> </div> </div> </div> diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss index f672fae74a1e3ad92cd954a468e61a3edd90a22f..88ec3a23ad9b929e11de58f6d297aeaeb2d9bb6a 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss @@ -169,4 +169,11 @@ mat-dialog-content { ::ng-deep .ng-animating div mat-accordion mat-expansion-panel div.mat-expansion-panel-content { height: 0px; visibility: hidden; -} \ No newline at end of file +} + +.reset { + margin: 25px auto auto auto; + background-color: var(--secondary); + color: black; + width: 100%; +} 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 cd57065fd6baa759b545a30cd0242f152e9486df..f4dd4d29d01c5a55d6dcd4f8ab70f94b113db03f 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 @@ -60,7 +60,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { startDate: string; endDate: string; - keywords: Keyword[] = []; + keywords: Map<string, Keyword> = new Map<string, Keyword>(); private topicCloudAdminData: TopicCloudAdminData; private profanityFilter: boolean; private censorPartialWordsCheck: boolean; @@ -101,19 +101,22 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.initializeKeywords(); } + getValues(): Keyword[] { + return [...this.keywords.values()]; + } + removeFromKeywords(comment: Comment) { - for (const keyword of this.keywords) { - keyword.comments.forEach(_comment => { - if (_comment.id === comment.id) { - keyword.comments.splice(keyword.comments.indexOf(comment, 0), 1); - } - }); + for (const [_, keyword] of this.keywords.entries()) { + const index = keyword.comments.findIndex(c => c.id === comment.id); + if (index >= 0) { + keyword.comments.splice(index, 1); + } } this.refreshKeywords(); } refreshKeywords() { - this.keywords = []; + this.keywords = new Map<string, Keyword>(); this.roomDataService.currentRoomData.forEach(comment => { this.pushInKeywords(comment); }); @@ -138,28 +141,30 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { keywords = []; } keywords.forEach(_keyword => { - const existingKey = this.checkIfKeywordExists(_keyword); + const existingKey = this.checkIfKeywordExists(_keyword.lemma); if (existingKey) { existingKey.vote += comment.score; + _keyword.dep.forEach(dep => existingKey.keywordDeps.add(dep)); if (this.checkIfCommentExists(existingKey.comments, comment.id)) { existingKey.comments.push(comment); } } else { if (this.keywordORfulltext === KeywordOrFulltext[KeywordOrFulltext.both]) { - if (comment.keywordsFromQuestioner.includes(_keyword) && comment.keywordsFromSpacy.includes(_keyword)) { + const includedFromQuestioner = comment.keywordsFromQuestioner.findIndex(e => e.lemma === _keyword.lemma) >= 0; + if (includedFromQuestioner && comment.keywordsFromSpacy.findIndex(e => e.lemma === _keyword.lemma) >= 0) { _keywordType = KeywordType.fromBoth; } else { - _keywordType = comment.keywordsFromQuestioner.includes(_keyword) ? KeywordType.fromQuestioner : KeywordType.fromSpacy; + _keywordType = includedFromQuestioner ? KeywordType.fromQuestioner : KeywordType.fromSpacy; } } - const keyword: Keyword = { - keyword: _keyword, + this.keywords.set(_keyword.lemma, { + keyword: _keyword.lemma, + keywordDeps: new Set<string>(_keyword.dep), keywordType: _keywordType, - keywordWithoutProfanity: this.getKeywordWithoutProfanity(_keyword, comment.language), + keywordWithoutProfanity: this.getKeywordWithoutProfanity(_keyword.lemma, comment.language), comments: [comment], vote: comment.score - }; - this.keywords.push(keyword); + } as Keyword); } }); } @@ -199,7 +204,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { if (comments === null) { return; } - this.keywords = []; + this.keywords = new Map<string, Keyword>(); comments.forEach(comment => { this.pushInKeywords(comment); }); @@ -324,21 +329,23 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { if (sortMode !== undefined) { this.sortMode = sortMode; } + const entries = [...this.keywords.entries()]; switch (this.sortMode) { case 'alphabetic': - this.keywords.sort((a, b) => a.keyword.localeCompare(b.keyword)); + entries.sort(([a], [b]) => a.localeCompare(b)); break; case 'questionsCount': - this.keywords.sort((a, b) => b.comments.length - a.comments.length); + entries.sort(([_, a], [__, b]) => b.comments.length - a.comments.length); break; case 'voteCount': - this.keywords.sort((a, b) => b.vote - a.vote); + entries.sort(([_, a], [__, b]) => b.vote - a.vote); break; } + this.keywords = new Map(entries); } checkIfThereAreQuestions() { - if (this.keywords.length === 0) { + if (this.keywords.size === 0) { this.translateService.get('topic-cloud-dialog.no-keywords-note').subscribe(msg => { this.notificationService.show(msg); }); @@ -359,10 +366,10 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { key.comments.forEach(comment => { const changes = new TSMap<string, any>(); let keywords = comment.keywordsFromQuestioner; - keywords.splice(keywords.indexOf(key.keyword, 0), 1); + keywords.splice(keywords.findIndex(e => e.lemma === key.keyword), 1); changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); keywords = comment.keywordsFromSpacy; - keywords.splice(keywords.indexOf(key.keyword, 0), 1); + keywords.splice(keywords.findIndex(e => e.lemma === key.keyword), 1); changes.set('keywordsFromSpacy', JSON.stringify(keywords)); this.updateComment(comment, changes, message); }); @@ -374,12 +381,12 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string) { this.commentService.patchComment(updatedComment, changes).subscribe(_ => { - if (messageTranslate) { - this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { - this.notificationService.show(msg); - }); - } - }, + if (messageTranslate) { + this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { + this.notificationService.show(msg); + }); + } + }, error => { this.translateService.get('topic-cloud-dialog.changes-gone-wrong').subscribe(msg => { this.notificationService.show(msg); @@ -395,21 +402,25 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { confirmEdit(key: Keyword): void { const key2 = this.checkIfKeywordExists(this.newKeyword); if (key2) { + if (key === key2) { + return; + } this.openConfirmDialog('merge-message', 'merge', key, key2); } else { key.comments.forEach(comment => { const changes = new TSMap<string, any>(); let keywords = comment.keywordsFromQuestioner; - for (let i = 0; i < keywords.length; i++) { - if (keywords[i].toLowerCase() === key.keyword.toLowerCase()) { - keywords[i] = this.newKeyword.trim(); + const lowerCaseKeyword = key.keyword.toLowerCase(); + for (const keyword of keywords) { + if (keyword.lemma.toLowerCase() === lowerCaseKeyword) { + keyword.lemma = this.newKeyword.trim(); } } changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); keywords = comment.keywordsFromSpacy; - for (let i = 0; i < keywords.length; i++) { - if (keywords[i].toLowerCase() === key.keyword.toLowerCase()) { - keywords[i] = this.newKeyword.trim(); + for (const keyword of keywords) { + if (keyword.lemma.toLowerCase() === lowerCaseKeyword) { + keyword.lemma = this.newKeyword.trim(); } } changes.set('keywordsFromSpacy', JSON.stringify(keywords)); @@ -444,35 +455,45 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { if (!this.searchedKeyword) { this.searchMode = false; } else { - this.filteredKeywords = this.keywords.filter(keyword => + const entries = [...this.keywords.entries()]; + this.filteredKeywords = entries.filter(([_, keyword]) => keyword.keyword.toLowerCase().includes(this.searchedKeyword.toLowerCase()) - ); + ).map(e => e[1]); this.searchMode = true; } } mergeKeywords(key1: Keyword, key2: Keyword) { if (key1 !== undefined && key2 !== undefined) { - key1.comments.forEach(comment => { + key1.comments = key1.comments.filter(comment => { if (this.checkIfCommentExists(key2.comments, comment.id)) { const changes = new TSMap<string, any>(); + const lowerKey1 = key1.keyword.toLowerCase(); + let keywords = comment.keywordsFromQuestioner; - keywords.push(key2.keyword); + let index = keywords.findIndex(k => k.lemma.toLowerCase() === lowerKey1); + keywords[index].lemma = key2.keyword; changes.set('keywordsFromQuestioner', JSON.stringify(keywords)); + keywords = comment.keywordsFromSpacy; - keywords.push(key2.keyword); + index = keywords.findIndex(k => k.lemma.toLowerCase() === lowerKey1); + keywords[index].lemma = key2.keyword; changes.set('keywordsFromSpacy', JSON.stringify(keywords)); + this.updateComment(comment, changes); + return false; } + return true; }); this.deleteKeyword(key1); } } checkIfKeywordExists(key: string): Keyword { - for (const keyword of this.keywords) { - if (keyword.keyword.toLowerCase() === key.trim().toLowerCase()) { - return keyword; + const currentKeyword = key.toLowerCase(); + for (const keyword of this.keywords.keys()) { + if (keyword.toLowerCase() === currentKeyword) { + return this.keywords.get(keyword); } } return undefined; @@ -550,6 +571,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { interface Keyword { keyword: string; + keywordDeps: Set<string>; keywordType: KeywordType; keywordWithoutProfanity: string; comments: Comment[]; 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 6b869eb929b740b59d14729a5ac283cfdf3efbc6..6abf38ae94368566607da768f99e8ab0f75e5ad6 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 @@ -9,7 +9,7 @@ <div class="elementText"> {{(!isMobile() ? 'content.continue-with-all-questions' : 'content.continue-with-all-questions-short') | translate}} </div> - <div class="elementIcons"> + <div class="elementIcons" *ngIf="allComments"> <mat-icon [inline]="true" matTooltip="{{'header.overview-question-tooltip' | translate}}">comment</mat-icon> {{allComments.comments}} <mat-icon [inline]="true" @@ -24,7 +24,7 @@ <div class="elementText"> {{(!isMobile() ? 'content.continue-with-current-questions' : 'content.continue-with-current-questions-short')| translate}} </div> - <div class="elementIcons"> + <div class="elementIcons" *ngIf="filteredComments"> <mat-icon [inline]="true" matTooltip="{{'header.overview-question-tooltip' | translate}}">comment</mat-icon> {{filteredComments.comments}} <mat-icon [inline]="true" 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 5bfdb4b459ff80efb336bd8635a6daed705b248d..ae6f6f9c8e150a3ab4041e153f03215551890c77 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 @@ -1,5 +1,5 @@ import { Room } from '../../../../models/room'; -import { Model, SpacyService } from '../../../../services/http/spacy.service'; +import { Model, SpacyKeyword, SpacyService } from '../../../../services/http/spacy.service'; import { CommentService } from '../../../../services/http/comment.service'; import { Comment } from '../../../../models/comment'; import { Language, LanguagetoolService } from '../../../../services/http/languagetool.service'; @@ -9,6 +9,12 @@ import { HttpErrorResponse } from '@angular/common/http'; const concurrentCallsPerTask = 4; +enum FinishType { + completed, + badSpelled, + failed +} + export class WorkerDialogTask { error: string = null; @@ -55,41 +61,48 @@ export class WorkerDialogTask { CreateCommentKeywords.isSpellingAcceptable(this.languagetoolService, currentComment.body) .subscribe(result => { if (!result.isAcceptable) { - this.statistics.badSpelled++; - this.callSpacy(currentIndex + concurrentCallsPerTask); + this.finishSpacyCall(FinishType.badSpelled, currentIndex); return; } 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); + this.finishSpacyCall(FinishType.badSpelled, currentIndex); return; } this.spacyService.getKeywords(result.text, model) - .subscribe(newKeywords => { - const changes = new TSMap<string, string>(); - changes.set('keywordsFromSpacy', JSON.stringify(newKeywords)); - this.commentService.patchComment(currentComment, changes).subscribe(_ => { - this.statistics.succeeded++; - }, - patchError => { - this.statistics.failed++; - if (patchError instanceof HttpErrorResponse && patchError.status === 403) { - this.error = 'forbidden'; - } - }, () => { - this.callSpacy(currentIndex + concurrentCallsPerTask); - }); - }, - __ => { - this.statistics.failed++; - this.callSpacy(currentIndex + concurrentCallsPerTask); - }); - }, _ => { + .subscribe(newKeywords => this.finishSpacyCall(FinishType.completed, currentIndex, newKeywords), + __ => this.finishSpacyCall(FinishType.failed, currentIndex)); + }, _ => this.finishSpacyCall(FinishType.failed, currentIndex)); + } + + private finishSpacyCall(finishType: FinishType, index: number, tags?: SpacyKeyword[]): void { + if (finishType === FinishType.completed) { + this.statistics.succeeded++; + this.patchToServer(tags, index); + } else if (finishType === FinishType.badSpelled) { + this.statistics.badSpelled++; + this.patchToServer([], index); + } else { + this.statistics.failed++; + } + this.callSpacy(index + concurrentCallsPerTask); + } + + private patchToServer(tags: SpacyKeyword[], index: number) { + const changes = new TSMap<string, string>(); + changes.set('keywordsFromSpacy', JSON.stringify(tags)); + this.commentService.patchComment(this._comments[index], changes).subscribe(_ => { + this.statistics.succeeded++; + }, + patchError => { this.statistics.failed++; - this.callSpacy(currentIndex + concurrentCallsPerTask); + if (patchError instanceof HttpErrorResponse && patchError.status === 403) { + this.error = 'forbidden'; + } + }, () => { + this.callSpacy(index + concurrentCallsPerTask); }); } 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 a20a0867daaa767446e0c2c087ab15c9a5523a91..17791bd18b2450e7d0667d1185c4e4da79cb7faa 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -78,6 +78,7 @@ export class CommentListComponent implements OnInit, OnDestroy { unanswered = 'unanswered'; owner = 'owner'; currentFilter = ''; + currentFilterCompare: any = null; commentVoteMap = new Map<string, Vote>(); scroll = false; scrollExtended = false; @@ -371,6 +372,7 @@ export class CommentListComponent implements OnInit, OnDestroy { filterComments(type: string, compare?: any): void { this.currentFilter = type; + this.currentFilterCompare = compare; if (type === '') { this.filteredComments = this.commentsFilteredByTime; this.hideCommentsList = false; @@ -402,8 +404,8 @@ export class CommentListComponent implements OnInit, OnDestroy { return c.userNumber === compare; case this.keyword: this.selectedKeyword = compare; - const isInQuestioner = c.keywordsFromQuestioner ? c.keywordsFromQuestioner.includes(compare) : false; - const isInSpacy = c.keywordsFromSpacy ? c.keywordsFromSpacy.includes(compare) : false; + const isInQuestioner = c.keywordsFromQuestioner ? c.keywordsFromQuestioner.findIndex(k => k.lemma === compare) >= 0 : false; + const isInSpacy = c.keywordsFromSpacy ? c.keywordsFromSpacy.findIndex(k => k.lemma === compare) >= 0 : false; return isInQuestioner || isInSpacy; case this.answer: return c.answer; @@ -496,10 +498,10 @@ export class CommentListComponent implements OnInit, OnDestroy { subscribeCommentStream() { this.commentStream = this.roomDataService.receiveUpdates([ - {type: 'CommentCreated', finished: true}, - {type: 'CommentPatched', subtype: this.favorite}, - {type: 'CommentPatched', subtype: 'score'}, - {finished: true} + { type: 'CommentCreated', finished: true }, + { type: 'CommentPatched', subtype: this.favorite }, + { type: 'CommentPatched', subtype: 'score' }, + { finished: true } ]).subscribe(update => { if (update.type === 'CommentCreated') { this.announceNewComment(update.comment.body); @@ -591,7 +593,7 @@ export class CommentListComponent implements OnInit, OnDestroy { this.commentsFilteredByTime = this.comments; } - this.filterComments(this.currentFilter); + this.filterComments(this.currentFilter, this.currentFilterCompare); this.titleService.attachTitle('(' + this.commentsFilteredByTime.length + ')'); } diff --git a/src/app/components/shared/comment/comment.component.html b/src/app/components/shared/comment/comment.component.html index 73f4485222293ad6b40631e6db42c5707255bcc3..3cb871d6950a6b9f18798623a9077ca069b2a0a5 100644 --- a/src/app/components/shared/comment/comment.component.html +++ b/src/app/components/shared/comment/comment.component.html @@ -342,8 +342,8 @@ <mat-list dense class="keywords-list"> <mat-list-item *ngFor="let keyword of sortKeywords(comment.keywordsFromQuestioner); let odd = odd; let even = even" [class.keywords-alternate]="odd" - [class.keywords-even]="even"> - <span (click)="this.clickedOnKeyword.emit(keyword)" class="keyword-span">{{keyword}}</span> + [class.keywords-even]="even"> + <span (click)="this.clickedOnKeyword.emit(keyword.lemma)" class="keyword-span">{{keyword.lemma}}</span> </mat-list-item> </mat-list> </mat-menu> diff --git a/src/app/components/shared/comment/comment.component.ts b/src/app/components/shared/comment/comment.component.ts index c675aabe36bd77e15998dd2b5cc4feba85df09ad..ca06a69ac583a44ed8efa4b21aa249fe19b8ce88 100644 --- a/src/app/components/shared/comment/comment.component.ts +++ b/src/app/components/shared/comment/comment.component.ts @@ -18,6 +18,7 @@ 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'; +import { SpacyKeyword } from '../../../services/http/spacy.service'; @Component({ selector: 'app-comment', @@ -128,8 +129,8 @@ export class CommentComponent implements OnInit, AfterViewInit { } } - sortKeywords(keywords: string[]){ - return keywords.sort((a,b) => a.localeCompare(b)); + sortKeywords(keywords: SpacyKeyword[]){ + return keywords.sort((a,b) => a.lemma.localeCompare(b.lemma)); } toggleExpand(evt: MouseEvent) { diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts index 81c54354ecc5a87a8cfbe38a874e45444f340189..2ce0f4b17f2f8f66bf58157ff179d603aae79953 100644 --- a/src/app/components/shared/header/header.component.ts +++ b/src/app/components/shared/header/header.component.ts @@ -152,6 +152,7 @@ export class HeaderComponent implements OnInit { }); this.moderationEnabled = (localStorage.getItem('moderationEnabled') === 'true') ? true : false; + this._r.listen(document, 'keyup', (event) => { if ( document.getElementById('back-button') && 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 7dd38350aa990e372eedca47a6bfbfd1467e7ba8..578a141f9cfbf68bf5c8fb495d5446708821ff45 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 @@ -16,7 +16,7 @@ {{tagData && tagData.distinctUsers.size}} </p> </span> - <button *ngIf="user && user.role >= 1" mat-button (click)="addBlacklistWord()"> + <button *ngIf="user && user.role >= 1 && isBlacklistActive" mat-button (click)="addBlacklistWord()"> <mat-icon matTooltip="{{'tag-cloud.blacklist-topic' | translate}}">gavel</mat-icon> </button> </div> 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 3b8c9e6eac50c42346b1f070f57b1dafcf0171e4..14b4e30c6cb2ac0eaa167288166eea0f7f705e3e 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 @@ -11,6 +11,7 @@ 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'; +import { SpacyKeyword } from '../../../../services/http/spacy.service'; const CLOSE_TIME = 1500; @@ -31,6 +32,7 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { user: User; selectedLang: Language = 'en-US'; spellingData: string[] = []; + isBlacklistActive = true; private _popupHoverTimer: number; private _popupCloseTimer: number; private _hasLeft = true; @@ -77,7 +79,10 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { this.close(); } - enter(elem: HTMLElement, tag: string, tagData: TagCloudDataTagEntry, hoverDelayInMs: number): void { + enter(elem: HTMLElement, tag: string, tagData: TagCloudDataTagEntry, hoverDelayInMs: number, isBlacklistActive: boolean): void { + if (!elem) { + return; + } this.spellingData = []; if (this.user && this.user.role > UserRole.PARTICIPANT) { this.languagetoolService.checkSpellings(tag, 'auto').subscribe(correction => { @@ -103,6 +108,7 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { this.categories = Array.from(tagData.categories.keys()); this.calculateDateText(() => { this.position(elem); + this.isBlacklistActive = isBlacklistActive; }); }, hoverDelayInMs); } @@ -143,22 +149,22 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { return; } const tagReplacementInput = this.replacementInput.value.trim(); - const renameKeyword = (elem: string, index: number, array: string[]) => { - if (elem === this.tag) { - array[index] = tagReplacementInput; + const renameKeyword = (elem: SpacyKeyword) => { + if (elem.lemma === this.tag) { + elem.lemma = tagReplacementInput; } }; const tagReplacementInputLower = tagReplacementInput.toLowerCase(); this.tagData.comments.forEach(comment => { const changes = new TSMap<string, any>(); - if (comment.keywordsFromQuestioner.findIndex(e => e.toLowerCase() === tagReplacementInputLower) >= 0) { - comment.keywordsFromQuestioner = comment.keywordsFromQuestioner.filter(e => e !== this.tag); + if (comment.keywordsFromQuestioner.findIndex(e => e.lemma.toLowerCase() === tagReplacementInputLower) >= 0) { + comment.keywordsFromQuestioner = comment.keywordsFromQuestioner.filter(e => e.lemma !== this.tag); } else { comment.keywordsFromQuestioner.forEach(renameKeyword); } changes.set('keywordsFromQuestioner', JSON.stringify(comment.keywordsFromQuestioner)); - if (comment.keywordsFromSpacy.findIndex(e => e.toLowerCase() === tagReplacementInputLower) >= 0) { - comment.keywordsFromSpacy = comment.keywordsFromSpacy.filter(e => e !== this.tag); + if (comment.keywordsFromSpacy.findIndex(e => e.lemma.toLowerCase() === tagReplacementInputLower) >= 0) { + comment.keywordsFromSpacy = comment.keywordsFromSpacy.filter(e => e.lemma !== this.tag); } else { comment.keywordsFromSpacy.forEach(renameKeyword); } 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 1722a332c74b757c09894757c64ddc892135f68d..90ef494e49ca4c0d40570ae3989eb3f6458c832a 100644 --- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts +++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts @@ -112,18 +112,25 @@ const getResolvedDefaultColors = (): string[] => { const getDefaultCloudParameters = (): CloudParameters => { const resDefaultColors = getResolvedDefaultColors(); + const minValue = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight; + const isMobile = minValue < 500; + const elements = isMobile ? 5 : -1; const weightSettings: CloudWeightSettings = [ - {maxVisibleElements: -1, color: resDefaultColors[1], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[2], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[3], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[4], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[5], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[6], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[7], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[8], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[9], rotation: 0, allowManualTagNumber: false}, - {maxVisibleElements: -1, color: resDefaultColors[10], rotation: 0, allowManualTagNumber: false}, + { maxVisibleElements: elements, color: resDefaultColors[1], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[2], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[3], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[4], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[5], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[6], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[7], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[8], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[9], rotation: 0, allowManualTagNumber: isMobile }, + { maxVisibleElements: elements, color: resDefaultColors[10], rotation: 0, allowManualTagNumber: isMobile }, ]; + const mapValue = (current: number, minInputValue: number, maxInputValue: number, minOut: number, maxOut: number) => { + const value = (current - minInputValue) * (maxOut - minOut) / (maxInputValue - minInputValue) + minOut; + return Math.min(maxOut, Math.max(minOut, value)); + }; return { fontFamily: 'Dancing Script', fontWeight: 'normal', @@ -131,9 +138,9 @@ const getDefaultCloudParameters = (): CloudParameters => { fontSize: '10px', backgroundColor: resDefaultColors[11], fontColor: resDefaultColors[0], - fontSizeMin: 150, - fontSizeMax: 900, - hoverScale: 2, + fontSizeMin: mapValue(minValue, 375, 750, 125, 200), + fontSizeMax: mapValue(minValue, 375, 1500, 300, 900), + hoverScale: mapValue(minValue, 375, 1500, 1.4, 2), hoverTime: 1, hoverDelay: 0.4, delayWord: 100, @@ -151,7 +158,7 @@ const getDefaultCloudParameters = (): CloudParameters => { }) export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, AfterViewInit { - @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent; + @ViewChild(TCloudComponent, { static: false }) child: TCloudComponent; @ViewChild(TagCloudPopUpComponent) popup: TagCloudPopUpComponent; @Input() user: User; @Input() roomId: string; @@ -270,6 +277,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, A const message = JSON.parse(msg.body); if (message.type === 'RoomPatched') { this.room.questionsBlocked = message.payload.changes.questionsBlocked; + this.room.blacklistIsActive = message.payload.changes.blacklistIsActive; } }); this.directSend = this.room.directSend; @@ -301,7 +309,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, A } ngAfterViewInit() { - this.rebuildData(); + setTimeout(() => this.rebuildData()); } ngOnDestroy() { @@ -343,7 +351,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, A } resetColorsToTheme() { - this.setCloudParameters(getDefaultCloudParameters()); + this.setCloudParameters(getDefaultCloudParameters(), true); } onResize(event: UIEvent): any { @@ -368,7 +376,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, A if (remaining > 0) { --countFiler[tagData.adjustedWeight]; } - let rotation = Math.random() < 0.5 ? this._currentSettings.cloudWeightSettings[tagData.adjustedWeight].rotation : 0; + let rotation = this._currentSettings.cloudWeightSettings[tagData.adjustedWeight].rotation; if (rotation === null || this._currentSettings.randomAngles) { rotation = Math.floor(Math.random() * 30 - 15); } @@ -420,7 +428,7 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, A this.eventService.broadcast('setTagConfig', tag.text); this._subscriptionCommentlist.unsubscribe(); }); - this.router.navigate(['../'], {relativeTo: this.route}); + this.router.navigate(['../'], { relativeTo: this.route }); } private updateAlphabeticalPosition(elements: TagComment[]): void { @@ -461,7 +469,8 @@ export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit, A elem.dataset['tempRotation'] = transformMatch ? transformMatch[1] : '0deg'; elem.style.transform = elem.style.transform.replace(transformationRotationKiller, '').trim(); this.popup.enter(elem, dataElement.text, dataElement.tagData, - (this._currentSettings.hoverTime + this._currentSettings.hoverDelay) * 1_000); + (this._currentSettings.hoverTime + this._currentSettings.hoverDelay) * 1_000, + this.room && this.room.blacklistIsActive); }); }); } diff --git a/src/app/models/comment.ts b/src/app/models/comment.ts index 8de26629ffa7d82ce9c47e34634dbe0b82b87de7..5666ae256ff394f9dd3ccdfb7951688f4deda55e 100644 --- a/src/app/models/comment.ts +++ b/src/app/models/comment.ts @@ -1,4 +1,4 @@ -import { Model } from '../services/http/spacy.service'; +import { Model, SpacyKeyword } from '../services/http/spacy.service'; import { CorrectWrong } from './correct-wrong.enum'; export class Comment { @@ -20,8 +20,8 @@ export class Comment { answer: string; userNumber: number; number: number; - keywordsFromQuestioner: string[]; - keywordsFromSpacy: string[]; + keywordsFromQuestioner: SpacyKeyword[]; + keywordsFromSpacy: SpacyKeyword[]; upvotes: number; downvotes: number; language: Language; @@ -41,8 +41,8 @@ export class Comment { tag: string = '', answer: string = '', userNumber: number = 0, - keywordsFromQuestioner: string[] = [], - keywordsFromSpacy: string[] = [], + keywordsFromQuestioner: SpacyKeyword[] = [], + keywordsFromSpacy: SpacyKeyword[] = [], upvotes = 0, downvotes = 0, language = Language.auto) { diff --git a/src/app/services/http/spacy.service.ts b/src/app/services/http/spacy.service.ts index 3cd7101da0c520a1ea555a2f1b3c4211bef5bff1..6cf3fe6169d3c2debea9dc490c49cc306d292785 100644 --- a/src/app/services/http/spacy.service.ts +++ b/src/app/services/http/spacy.service.ts @@ -3,11 +3,15 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { BaseHttpService } from './base-http.service'; import { catchError, map, tap } from 'rxjs/operators'; -import { TopicCloudAdminService } from '../util/topic-cloud-admin.service'; import { CreateCommentKeywords } from '../../utils/create-comment-keywords'; export type Model = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pt' | 'auto'; +export interface SpacyKeyword { + lemma: string; + dep: string[]; +} + type EnglishParserLabels = 'ROOT' | //None 'acl' | //clausal modifier of noun (adjectival clause) 'acomp' | //adjectival complement @@ -161,7 +165,7 @@ type KeywordList = AbstractKeyword[]; const httpOptions = { // eslint-disable-next-line @typescript-eslint/naming-convention - headers: new HttpHeaders({'Content-Type': 'application/json'}) + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @Injectable({ @@ -177,17 +181,31 @@ export class SpacyService extends BaseHttpService { return LABELS[model]; } - getKeywords(text: string, model: Model): Observable<string[]> { + getKeywords(text: string, model: Model): Observable<SpacyKeyword[]> { const url = '/spacy'; - const wanted = TopicCloudAdminService.getDefaultAdminData.wantedLabels[model]; - return this.http.post<KeywordList>(url, {text, model}, httpOptions) + return this.http.post<KeywordList>(url, { text, model }, httpOptions) .pipe( tap(_ => ''), catchError(this.handleError<any>('getKeywords')), - map((elem: KeywordList) => wanted != null ? - elem.filter(e => wanted.includes(e.dep) && CreateCommentKeywords.isKeywordAcceptable(e.lemma)) : - elem), - map((result: KeywordList) => [...new Set(result.map(e => e.lemma.trim()))]) + map((elem: KeywordList) => { + const keywordsMap = new Map<string, { lemma: string; dep: Set<string> }>(); + elem.forEach(e => { + const keyword = e.lemma.trim(); + if (!CreateCommentKeywords.isKeywordAcceptable(keyword)) { + return; + } + let keywordObj = keywordsMap.get(keyword); + if (!keywordObj) { + keywordObj = { + lemma: keyword, + dep: new Set<string>() + }; + keywordsMap.set(keyword, keywordObj); + } + keywordObj.dep.add(e.dep); + }); + return [...keywordsMap.values()].map(e => ({ lemma: e.lemma, dep: [...e.dep] })); + }) ); } } diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts index 20a1bad7ec6f4dc5016843ba56b9e870986e7a6d..1530f4e58e735f1a1f917e877eca77b02a135fbb 100644 --- a/src/app/services/util/room-data.service.ts +++ b/src/app/services/util/room-data.service.ts @@ -265,6 +265,7 @@ export class RoomDataService { c.creatorId = payload.creatorId; c.userNumber = this.commentService.hashCode(c.creatorId); c.keywordsFromQuestioner = JSON.parse(payload.keywordsFromQuestioner); + c.language = payload.language; this._fastCommentAccess[c.id] = c; this._currentComments.push(c); this.triggerUpdate(UpdateType.commentStream, { diff --git a/src/app/services/util/tag-cloud-data.service.ts b/src/app/services/util/tag-cloud-data.service.ts index 8d48d73ab0b051a7d02d7a56ac8fcbb8ff56085d..0c9373b74b5f4d9dadbf09c8c1745cc3fa0c4056 100644 --- a/src/app/services/util/tag-cloud-data.service.ts +++ b/src/app/services/util/tag-cloud-data.service.ts @@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CloudParameters } from '../../components/shared/tag-cloud/tag-cloud.interface'; import { Comment } from '../../models/comment'; import { RoomDataService } from './room-data.service'; +import { SpacyKeyword } from '../http/spacy.service'; export interface TagCloudDataTagEntry { weight: number; @@ -18,6 +19,7 @@ export interface TagCloudDataTagEntry { firstTimeStamp: Date; lastTimeStamp: Date; categories: Set<string>; + dependencies: Set<string>; comments: Comment[]; } @@ -54,6 +56,8 @@ export enum TagCloudCalcWeightType { byLengthAndVotes } +const DEBOUNCE_TIME = 3_000; + @Injectable({ providedIn: 'root' }) @@ -74,6 +78,8 @@ export class TagCloudDataService { private _adminData: TopicCloudAdminData = null; private _subscriptionAdminData: Subscription; private _currentFilter: CommentFilter; + private _debounceTimer = 0; + private _lastDebounceTime = new Date().getTime() - DEBOUNCE_TIME; constructor(private _tagCloudAdmin: TopicCloudAdminService, private _roomDataService: RoomDataService) { @@ -100,8 +106,8 @@ export class TagCloudDataService { const data: TagCloudData = new Map<string, TagCloudDataTagEntry>(); const users = new Set<number>(); for (const comment of comments) { - TopicCloudAdminService.approveKeywordsOfComment(comment, adminData, (keyword) => { - let current: TagCloudDataTagEntry = data.get(keyword); + TopicCloudAdminService.approveKeywordsOfComment(comment, adminData, (keyword: SpacyKeyword) => { + let current: TagCloudDataTagEntry = data.get(keyword.lemma); const commentDate = new Date(comment.timestamp); if (current === undefined) { current = { @@ -113,11 +119,13 @@ export class TagCloudDataService { adjustedWeight: 0, distinctUsers: new Set<number>(), categories: new Set<string>(), + dependencies: new Set<string>([...keyword.dep]), firstTimeStamp: commentDate, lastTimeStamp: commentDate }; - data.set(keyword, current); + data.set(keyword.lemma, current); } + keyword.dep.forEach(dependency => current.dependencies.add(dependency)); current.cachedVoteCount += comment.score; current.cachedUpVotes += comment.upvotes; current.cachedDownVotes += comment.downvotes; @@ -150,19 +158,20 @@ export class TagCloudDataService { this._subscriptionAdminData = this._tagCloudAdmin.getAdminData.subscribe(adminData => { this.onReceiveAdminData(adminData, true); }); + this._tagCloudAdmin.ensureRoomBound(roomId); this.fetchData(); if (!this._currentFilter.paused) { this._commentSubscription = this._roomDataService.receiveUpdates([ - {type: 'CommentCreated', finished: true}, - {type: 'CommentDeleted'}, - {type: 'CommentPatched', finished: true, updates: ['score']}, - {type: 'CommentPatched', finished: true, updates: ['downvotes']}, - {type: 'CommentPatched', finished: true, updates: ['upvotes']}, - {type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy']}, - {type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner']}, - {type: 'CommentPatched', finished: true, updates: ['ack']}, - {type: 'CommentPatched', finished: true, updates: ['tag']}, + { type: 'CommentCreated', finished: true }, + { type: 'CommentDeleted' }, + { type: 'CommentPatched', finished: true, updates: ['score'] }, + { type: 'CommentPatched', finished: true, updates: ['downvotes'] }, + { type: 'CommentPatched', finished: true, updates: ['upvotes'] }, + { type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy'] }, + { type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner'] }, + { type: 'CommentPatched', finished: true, updates: ['ack'] }, + { type: 'CommentPatched', finished: true, updates: ['tag'] }, ]).subscribe(_ => { this.rebuildTagData(); }); @@ -190,6 +199,7 @@ export class TagCloudDataService { adjustedWeight: i - 1, categories: new Set<string>(), distinctUsers: new Set<number>(), + dependencies: new Set<string>(), firstTimeStamp: new Date(), lastTimeStamp: new Date() }); @@ -281,7 +291,18 @@ export class TagCloudDataService { newData = new Map<string, TagCloudDataTagEntry>([...current] .sort(([_, aTagData], [__, bTagData]) => bTagData.weight - aTagData.weight)); } - this._dataBus.next(newData); + const currentTime = new Date().getTime(); + const diff = currentTime - this._lastDebounceTime; + if (diff >= DEBOUNCE_TIME) { + this._dataBus.next(newData); + this._lastDebounceTime = currentTime; + } else { + clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(() => { + this._dataBus.next(newData); + this._lastDebounceTime = new Date().getTime(); + }, DEBOUNCE_TIME - diff); + } } private onReceiveAdminData(data: TopicCloudAdminData, update = false) { diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index e21964674fe463c9cb810dbceccc51e87817bc73..84cd5e70e621154544ed96590d786c912f54a202 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -10,7 +10,7 @@ import { TranslateService } from '@ngx-translate/core'; import { NotificationService } from './notification.service'; import { WsRoomService } from '../websockets/ws-room.service'; import { ProfanityFilterService } from './profanity-filter.service'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; import { Comment } from '../../models/comment'; @Injectable({ @@ -22,6 +22,8 @@ export class TopicCloudAdminService { private blacklist: Subject<string[]>; private blacklistIsActive: Subject<boolean>; private blacklistActive: boolean; + private _subscriptionWsRoom: Subscription; + constructor(private roomService: RoomService, private translateService: TranslateService, private wsRoomService: WsRoomService, @@ -29,22 +31,10 @@ export class TopicCloudAdminService { private notificationService: NotificationService) { this.blacklist = new Subject<string[]>(); this.blacklistIsActive = new Subject<boolean>(); - this.wsRoomService.getRoomStream(localStorage.getItem('roomId')).subscribe(msg => { - const message = JSON.parse(msg.body); - const room = message.payload.changes; - if (message.type === 'RoomPatched') { - this.blacklist.next(room.blacklist ? JSON.parse(room.blacklist) : []); - this.blacklistActive = room.blacklistIsActive; - this.blacklistIsActive.next(room.blacklistIsActive); - const data = TopicCloudAdminService.getDefaultAdminData; - data.blacklistIsActive = this.blacklistActive; - this.setAdminData(data, false); - } - }); this.adminData = new BehaviorSubject<TopicCloudAdminData>(TopicCloudAdminService.getDefaultAdminData); } - static approveKeywordsOfComment(comment: Comment, config: TopicCloudAdminData, keywordFunc: (string) => void) { + static approveKeywordsOfComment(comment: Comment, config: TopicCloudAdminData, keywordFunc: (SpacyKeyword) => void) { let source = comment.keywordsFromQuestioner; if (config.keywordORfulltext === KeywordOrFulltext.both) { source = !source || !source.length ? comment.keywordsFromSpacy : source; @@ -54,9 +44,13 @@ export class TopicCloudAdminService { if (!source) { return; } + const wantedLabels = config.wantedLabels[comment.language.toLowerCase()]; for (const keyword of source) { + if (wantedLabels && !keyword.dep.some(e => wantedLabels.includes(e))) { + continue; + } let isProfanity = false; - const lowerCasedKeyword = keyword.toLowerCase(); + const lowerCasedKeyword = keyword.lemma.toLowerCase(); for (const word of config.blacklist) { if (lowerCasedKeyword.includes(word)) { isProfanity = true; @@ -130,6 +124,26 @@ export class TopicCloudAdminService { return this.adminData.asObservable(); } + ensureRoomBound(roomId: string) { + if (this._subscriptionWsRoom) { + this._subscriptionWsRoom.unsubscribe(); + this._subscriptionWsRoom = null; + } + this._subscriptionWsRoom = this.wsRoomService.getRoomStream(roomId).subscribe(msg => { + const message = JSON.parse(msg.body); + const room = message.payload.changes; + if (message.type === 'RoomPatched') { + this.blacklist.next(room.blacklist ? JSON.parse(room.blacklist) : []); + this.blacklistActive = room.blacklistIsActive; + this.blacklistIsActive.next(room.blacklistIsActive); + const data = TopicCloudAdminService.getDefaultAdminData; + data.profanityFilter = room.profanityFilter; + data.blacklistIsActive = this.blacklistActive; + this.setAdminData(data, false); + } + }); + } + setAdminData(_adminData: TopicCloudAdminData, updateRoom: boolean) { localStorage.setItem(TopicCloudAdminService.adminKey, JSON.stringify(_adminData)); if (updateRoom) { diff --git a/src/app/utils/create-comment-keywords.ts b/src/app/utils/create-comment-keywords.ts index bc48116b89158b29f0f0d704f5f52c6b391acfd5..02f8ae6e3cfac85bf124cc0d322653c508328622 100644 --- a/src/app/utils/create-comment-keywords.ts +++ b/src/app/utils/create-comment-keywords.ts @@ -5,7 +5,7 @@ export class CreateCommentKeywords { static isKeywordAcceptable(keyword: string) { const regex = /(^[ -@\[-`{-~]+$)/g; - return keyword.match(regex) === null; + return keyword.match(regex) === null && keyword.length; } static isSpellingAcceptable(languagetoolService: LanguagetoolService, text: string, language: Language = 'auto') { diff --git a/src/app/utils/filter-options.ts b/src/app/utils/filter-options.ts index d2791cb73b89eedd7b2f5e338f3870b100913957..af92e68b70c03a3d4c54a74d7f1adf1f057da2a8 100644 --- a/src/app/utils/filter-options.ts +++ b/src/app/utils/filter-options.ts @@ -164,7 +164,7 @@ export class CommentFilter { } if (this.keywordSelected !== '') { - return com.keywordsFromQuestioner.includes(this.keywordSelected); + return com.keywordsFromQuestioner.findIndex(e => e.lemma === this.keywordSelected) >= 0; } if (this.tagSelected !== '') { diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index e063663d4ee6645dc5ce299d144008f92e8869ca..4a790c3bb5552bb61a6e0d60917527eaf2b7e5a7 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -423,7 +423,8 @@ "topic-requirement-questioners": "Minimale Anzahl Fragensteller*innen", "topic-requirement-upvotes": "Minimale Anzahl Up-Votes", "topic-requirement-begin-datetime": "Beginn der Zeitspanne", - "topic-requirement-end-datetime": "Ende der Zeitspanne" + "topic-requirement-end-datetime": "Ende der Zeitspanne", + "topic-requirement-reset": "Zurücksetzen" }, "topic-cloud-confirm-dialog": { "cancel": "Abbrechen", @@ -494,8 +495,8 @@ "notation-tooltip": "Einstellung der Schreibweise: klein, groß, wie vorgegeben", "alphabetical-sorting-tooltip": "Alphabetische Sortierung", "highestWeight-tooltip": "x Themen mit der höchsten Gewichtung anzeigen", - "rotate-weight": "Themen dieser Häufigkeitsgruppe zufällig um x Grad drehen", - "rotate-weight-tooltip": "Themen dieser Häufigkeitsgruppe zufällig um x Grad drehen", + "rotate-weight": "Themen dieser Häufigkeitsgruppe um x Grad drehen", + "rotate-weight-tooltip": "Themen dieser Häufigkeitsgruppe um x Grad drehen", "font":"Schrift", "reset-btn": "Auf Standardwerte setzen", "font-family-tooltip": "Schrift auswählen …", diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index 2f025a7940469c506c92fc47bde10bcdf5c79557..ae3434c96bd05fed577349be1aa2376cbaf59f91 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -430,7 +430,8 @@ "topic-requirement-questioners": "Minimum number of questioners", "topic-requirement-upvotes": "Minimum number of upvotes", "topic-requirement-begin-datetime": "Earliest comment", - "topic-requirement-end-datetime": "Last comment" + "topic-requirement-end-datetime": "Last comment", + "topic-requirement-reset": "Reset" }, "topic-cloud-confirm-dialog":{ "cancel": "Cancel", @@ -490,15 +491,14 @@ "highestWeight": "Number of tags with highest weight", "notation-tooltip": "Notation-Settings: small, large, standard", "alphabetical-sorting-tooltip": "Alphabetical sorting", - "rotate-weight-tooltip": "Rotate some entries randomly by x degrees", "highestWeight-tooltip": "show x tags with the highest weight", - "rotate-weight": "Rotate some entries of this weight class randomly by x degrees", + "rotate-weight": "Rotate all entries of this class by x degrees", + "rotate-weight-tooltip": "Rotate all entries of this class by x degrees", "font":"Font", "reset-btn": "Reset", "font-family-tooltip": "Select font", "bold-notation-tooltip": "Select font-thickness bold", "font-style-bold" : "Bold", - "font-style-italic": "Italic", "font-family":"Font Family", "manual-weight-number": "Set quantity", "manual-weight-number-tooltip": "Quantity Limitation of this weight class", diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 0505fe2b0b37d1353cd3540f36c8c811e229a60c..187ecce9e070aa2367594f4b22ee56c66c8e24df 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -307,7 +307,8 @@ "topic-requirement-questioners": "Minimale Anzahl Fragensteller*innen", "topic-requirement-upvotes": "Minimale Anzahl Up-Votes", "topic-requirement-begin-datetime": "Beginn der Zeitspanne", - "topic-requirement-end-datetime": "Ende der Zeitspanne" + "topic-requirement-end-datetime": "Ende der Zeitspanne", + "topic-requirement-reset": "Zurücksetzen" }, "dialog-comment": { "read-more": "Mehr lesen", @@ -361,8 +362,8 @@ "notation-tooltip": "Einstellung der Schreibweise: klein, groß, wie vorgegeben", "alphabetical-sorting-tooltip": "Alphabetische Sortierung", "highestWeight-tooltip": "x Themen mit der höchsten Gewichtung anzeigen", - "rotate-weight": "Themen dieser Häufigkeitsgruppe zufällig um x Grad drehen", - "rotate-weight-tooltip": "Themen dieser Häufigkeitsgruppe zufällig um x Grad drehen", + "rotate-weight": "Themen dieser Häufigkeitsgruppe um x Grad drehen", + "rotate-weight-tooltip": "Themen dieser Häufigkeitsgruppe um x Grad drehen", "font":"Schrift", "reset-btn": "Auf Standardwerte setzen", "font-family-tooltip": "Schrift auswählen …", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 11b915af1c9c9539c41ea633300286c399eb3e39..012260e357b8a19fddedf3fdc24d9bfe2bb2ac2a 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -313,7 +313,8 @@ "topic-requirement-questioners": "Minimum number of questioners", "topic-requirement-upvotes": "Minimum number of upvotes", "topic-requirement-begin-datetime": "Earliest comment", - "topic-requirement-end-datetime": "Last comment" + "topic-requirement-end-datetime": "Last comment", + "topic-requirement-reset": "Reset" }, "dialog-comment":{ "read-more": "Read more", @@ -366,15 +367,14 @@ "highestWeight": "Number of tags with highest weight", "notation-tooltip": "Notation-Settings: small, large, standard", "alphabetical-sorting-tooltip": "Alphabetical sorting", - "rotate-weight-tooltip": "Rotate some entries randomly by x degrees", - "highestWeight-tooltip": "show x tags with the highest weight", - "rotate-weight": "Rotate some entries of this weight class randomly by x degrees", + "highestWeight-tooltip": "Show x tags with the highest weight", + "rotate-weight": "Rotate all entries of this class by x degrees", + "rotate-weight-tooltip": "Rotate all entries of this class by x degrees", "font":"Font", "reset-btn": "Reset", "font-family-tooltip": "Select font", "bold-notation-tooltip": "Select font-thickness bold", "font-style-bold" : "Bold", - "font-style-italic": "Italic", "font-family":"Font Family", "manual-weight-number": "Set quantity", "manual-weight-number-tooltip": "Quantity Limitation of this weight class",