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 c474784b14605b8f2cf210400d17bc242fd737ae..0850dd4b1c2c963df9e04b2b3d12e4923ff026dd 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 @@ -333,8 +333,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,14 +361,14 @@ </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}}"> 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 de5a10d1c30d4094c068f230c8f4163b417b962c..f8e54482ee03401f7cf52f68aa4239886584c7f1 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { NotificationService } from '../../../../services/util/notification.service'; import { TopicCloudConfirmDialogComponent } from '../topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component'; import { UserRole } from '../../../../models/user-roles.enum'; @@ -7,7 +7,7 @@ import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../../services/util/language.service'; import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; import { ProfanityFilterService } from '../../../../services/util/profanity-filter.service'; -import { TopicCloudAdminData, Labels, spacyLabels, KeywordOrFulltext } from './TopicCloudAdminData'; +import { KeywordOrFulltext, Labels, spacyLabels, TopicCloudAdminData } from './TopicCloudAdminData'; import { User } from '../../../../models/user'; import { Comment } from '../../../../models/comment'; import { CommentService } from '../../../../services/http/comment.service'; @@ -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,6 +101,10 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.initializeKeywords(); } + getValues(): Keyword[] { + return [...this.keywords.values()]; + } + changeblacklist() { this.topicCloudAdminService.getRoom().subscribe(room => { room.blacklistIsActive = this.blacklistIsActive; @@ -109,18 +113,17 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } 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); }); @@ -145,28 +148,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); } }); } @@ -187,7 +192,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); }); @@ -312,21 +317,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); }); @@ -347,10 +354,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); }); @@ -362,12 +369,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); @@ -388,16 +395,17 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { 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)); @@ -432,35 +440,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; @@ -538,6 +556,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 a1921b222bb4e9275c6674a5b5b27540320aa58f..17791bd18b2450e7d0667d1185c4e4da79cb7faa 100644 --- a/src/app/components/shared/comment-list/comment-list.component.ts +++ b/src/app/components/shared/comment-list/comment-list.component.ts @@ -404,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; @@ -498,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); diff --git a/src/app/components/shared/comment/comment.component.html b/src/app/components/shared/comment/comment.component.html index 00f1262ed0f5878f0291ad59fd6434b6dbb22a49..dcdf9bfdcaf5250b848cedd2382315ceb3847967 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/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 181c4ddac153b3dd198eaad12c77aebfd5d1faf9..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; @@ -79,6 +80,9 @@ export class TagCloudPopUpComponent implements OnInit, AfterViewInit { } 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 => { @@ -145,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/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..4e8b362c30b03c2631850da1c55f83b7c4748877 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[]; } @@ -100,8 +102,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 +115,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; @@ -190,6 +194,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() }); diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index 6a5b788b92611607650886e06e92e779552e3070..d630a2aa74ede52a85828c3303765f800af5ffa9 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -22,6 +22,7 @@ export class TopicCloudAdminService { private blacklist: Subject<string[]>; private blacklistIsActive: Subject<boolean>; private blacklistActive: boolean; + constructor(private roomService: RoomService, private translateService: TranslateService, private wsRoomService: WsRoomService, @@ -41,7 +42,7 @@ export class TopicCloudAdminService { 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; @@ -51,9 +52,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; 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 !== '') {