diff --git a/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.html b/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.html index b8d222ea2349d0e2977dfca00fc09d2b04354037..a2489068f100c7f1c4db88f3aa763b73658debe6 100644 --- a/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.html +++ b/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.html @@ -23,7 +23,10 @@ [maxDataCharacters]="data.maxDataCharacters" [isModerator]="data.isModerator" [isEditor]="true" - [currentData]="improvedValue.body"></app-view-comment-data> + [currentData]="improvedValue.body" + [usesFormality]="true" + [formalityEmitter]="onFormalityChange.bind(this)" + [selectedFormality]="'less'"></app-view-comment-data> </mat-radio-group> </div> diff --git a/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.ts b/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.ts index 2ccabc9798128b53f7e556277035ba5fbc1ceda0..b28a0db8cf4a968bcf964ea49a682ef965d641ae 100644 --- a/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.ts +++ b/src/app/components/shared/_dialogs/deep-ldialog/deep-ldialog.component.ts @@ -6,6 +6,10 @@ import { LanguageService } from '../../../../services/util/language.service'; import { TranslateService } from '@ngx-translate/core'; import { WriteCommentComponent } from '../../write-comment/write-comment.component'; import { ExplanationDialogComponent } from '../explanation-dialog/explanation-dialog.component'; +import { DeepLService, FormalityType, TargetLang } from '../../../../services/http/deep-l.service'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Target } from '@angular/compiler'; interface ResultValue { body: string; @@ -25,6 +29,7 @@ export class DeepLDialogComponent implements OnInit, AfterViewInit { radioButtonValue: ResultValue; normalValue: ResultValue; improvedValue: ResultValue; + supportsFormality: boolean; constructor( private dialogRef: MatDialogRef<DeepLDialogComponent>, @@ -32,10 +37,51 @@ export class DeepLDialogComponent implements OnInit, AfterViewInit { private notificationService: NotificationService, private languageService: LanguageService, private translateService: TranslateService, + private deeplService: DeepLService, private dialog: MatDialog) { this.languageService.langEmitter.subscribe(lang => { this.translateService.use(lang); }); + this.supportsFormality = DeepLService.supportsFormality(this.data.target); + } + + public static generateDeeplDelta(deepl: DeepLService, body: string, targetLang: TargetLang, + formality = FormalityType.less): Observable<[string, string]> { + const delta = ViewCommentDataComponent.getDeltaFromData(body); + const xml = delta.ops.reduce((acc, e, i) => { + if (typeof e['insert'] === 'string' && e['insert'].trim().length) { + acc += '<x i="' + i + '">' + this.encodeHTML(e['insert']) + '</x>'; + e['insert'] = ''; + } + return acc; + }, ''); + return deepl.improveTextStyle(xml, targetLang, formality).pipe( + map(str => { + const regex = /<x i="(\d+)">([^<]+)<\/x>/gm; + let m; + while ((m = regex.exec(str)) !== null) { + delta.ops[+m[1]]['insert'] += this.decodeHTML(m[2]); + } + const text = delta.ops.reduce((acc, el) => acc + (typeof el['insert'] === 'string' ? el['insert'] : ''), ''); + return [ViewCommentDataComponent.getDataFromDelta(delta), text]; + }) + ); + } + + private static encodeHTML(str: string): string { + return str.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + private static decodeHTML(str: string): string { + return str.replace(/'/g, '\'') + .replace(/"/g, '"') + .replace(/>/g, '>') + .replace(/</g, '<') + .replace(/&/g, '&'); } ngOnInit(): void { @@ -80,7 +126,7 @@ export class DeepLDialogComponent implements OnInit, AfterViewInit { if (WriteCommentComponent.checkInputData(current.body, current.text, this.translateService, this.notificationService, this.data.maxTextCharacters, this.data.maxDataCharacters)) { this.data.onClose(current.body, current.text, current.view); - this.dialogRef.close(); + this.dialogRef.close(true); } }; } @@ -92,4 +138,18 @@ export class DeepLDialogComponent implements OnInit, AfterViewInit { ref.componentInstance.translateKey = 'explanation.deepl'; } + onFormalityChange(formality: string) { + DeepLDialogComponent.generateDeeplDelta(this.deeplService, this.data.body, this.data.usedTarget, formality as FormalityType) + .subscribe(([improvedBody, improvedText]) => { + console.log(improvedBody, improvedText); + this.improvedValue.body = improvedBody; + this.improvedValue.text = improvedText; + this.improved.currentData = improvedBody; + }, (_) => { + this.translateService.get('deepl-formality-select.error').subscribe(str => { + this.notificationService.show(str); + }); + }); + } + } diff --git a/src/app/components/shared/_dialogs/quill-input-dialog/quill-input-dialog.component.ts b/src/app/components/shared/_dialogs/quill-input-dialog/quill-input-dialog.component.ts index 208868b77ec9d1298c41f7c9cb7881c793dfaa7f..e054bde4f3a204390a0769a73e98fa29917b6985 100644 --- a/src/app/components/shared/_dialogs/quill-input-dialog/quill-input-dialog.component.ts +++ b/src/app/components/shared/_dialogs/quill-input-dialog/quill-input-dialog.component.ts @@ -8,7 +8,7 @@ interface DialogData { meta: string; quill: any; selection: any; - overrideAction?: (value: string) => void; + overrideAction?: (value: string, selection: any) => void; } @Component({ @@ -52,7 +52,7 @@ export class QuillInputDialogComponent implements OnInit { buildConfirmAction() { return () => { if (this.data.overrideAction) { - this.data.overrideAction(this.value); + this.data.overrideAction(this.value, this.data.selection); this.dialogRef.close(); return; } diff --git a/src/app/components/shared/view-comment-data/view-comment-data.component.html b/src/app/components/shared/view-comment-data/view-comment-data.component.html index 75dd3999eef1096d7195f226f3a80904c96509e6..1220e9869b34912a367a3511c0096a91cef6a22f 100644 --- a/src/app/components/shared/view-comment-data/view-comment-data.component.html +++ b/src/app/components/shared/view-comment-data/view-comment-data.component.html @@ -5,6 +5,10 @@ [modules]="quillModules" (document:click)="onDocumentClick($event)" (window:resize)="recalcAspectRatio()"> + <div quill-editor-toolbar> + <ng-container [ngTemplateOutlet]="isModerator ? moderatorToolbar : participantToolbar"> + </ng-container> + </div> </quill-editor> <div #tooltipContainer></div> <div fxLayout="row" style="justify-content: flex-end; padding: 0 5px"> @@ -17,3 +21,64 @@ <quill-view #quillView [modules]="quillModules" (window:resize)="recalcAspectRatio()"> </quill-view> </div> + +<ng-template #participantToolbar> + <span class="ql-formats"> + <button type="button" class="ql-bold"></button> + <button type="button" class="ql-list" value="bullet"></button> + <button type="button" class="ql-list" value="ordered"></button> + <button type="button" class="ql-blockquote"></button> + <button type="button" class="ql-link" (click)="onClick($event, 'link')"></button> + <button type="button" class="ql-code-block"></button> + <button type="button" class="ql-formula" (click)="onClick($event, 'formula')"></button> + <button type="button" class="ql-emoji" *ngIf="hasEmoji"></button> + <mat-form-field class="deepl-form-field" *ngIf="usesFormality"> + <mat-label> + <mat-icon class="icon-svg"></mat-icon> + {{'deepl-formality-select.name' | translate}} + </mat-label> + <label for="deeplFormality"> + {{selectedFormality && ('deepl-formality-select.' + selectedFormality | translate)}} + </label> + <mat-select id="deeplFormality" [(ngModel)]="selectedFormality" + (selectionChange)="formalityEmitter && formalityEmitter(selectedFormality)"> + <mat-option>{{'deepl-formality-select.reset' | translate}}</mat-option> + <mat-option value="default">{{'deepl-formality-select.default' | translate}}</mat-option> + <mat-option value="less">{{'deepl-formality-select.less' | translate}}</mat-option> + <mat-option value="more">{{'deepl-formality-select.more' | translate}}</mat-option> + </mat-select> + </mat-form-field> + </span> +</ng-template> + +<ng-template #moderatorToolbar> + <span class="ql-formats"> + <button type="button" class="ql-bold"></button> + <select class="ql-color"></select> + <button type="button" class="ql-strike"></button> + <button type="button" class="ql-list" value="bullet"></button> + <button type="button" class="ql-list" value="ordered"></button> + <button type="button" class="ql-blockquote"></button> + <button type="button" class="ql-link" (click)="onClick($event, 'link')"></button> + <button type="button" class="ql-image" (click)="onClick($event, 'image')"></button> + <button type="button" class="ql-video" (click)="onClick($event, 'video')"></button> + <button type="button" class="ql-code-block"></button> + <button type="button" class="ql-formula" (click)="onClick($event, 'formula')"></button> + <button type="button" class="ql-emoji" *ngIf="hasEmoji"></button> + <mat-form-field class="deepl-form-field" *ngIf="usesFormality"> + <mat-label> + <mat-icon class="icon-svg"></mat-icon> + {{'deepl-formality-select.name' | translate}} + </mat-label> + <label for="deeplFormality"> + {{selectedFormality && ('deepl-formality-select.' + selectedFormality | translate)}} + </label> + <mat-select id="deeplFormality" [(ngModel)]="selectedFormality" + (selectionChange)="formalityEmitter && formalityEmitter(selectedFormality)"> + <mat-option value="default">{{'deepl-formality-select.default' | translate}}</mat-option> + <mat-option value="less">{{'deepl-formality-select.less' | translate}}</mat-option> + <mat-option value="more">{{'deepl-formality-select.more' | translate}}</mat-option> + </mat-select> + </mat-form-field> + </span> +</ng-template> diff --git a/src/app/components/shared/view-comment-data/view-comment-data.component.scss b/src/app/components/shared/view-comment-data/view-comment-data.component.scss index dcf3fc961a68a77048a5f8af2cc74c3d50662ac4..bc2014cd318b9ea1cbc623a6fd7fbf0d6893da7c 100644 --- a/src/app/components/shared/view-comment-data/view-comment-data.component.scss +++ b/src/app/components/shared/view-comment-data/view-comment-data.component.scss @@ -142,3 +142,49 @@ } } } + +#deeplFormality { + display: inline; +} + +.deepl-form-field { + @media screen and (max-width: 500px) { + width: 70px; + } + z-index: 10000; + height: 1em; +} + +::ng-deep .deepl-form-field .mat-form-field-wrapper { + position: absolute; + top: -1.35em; + left: 10px; + + @media only screen and (max-device-width: 480px) and (orientation: portrait), + only screen and (max-device-height: 480px) and (orientation: landscape) { + width: 70px; + top: -1em; + left: 0; + } +} + +.anchor-right { + @media screen and (max-width: 500px) { + width: 70px; + left: calc(100% - 70px); + } + width: 200px; + height: 50px; + position: relative; + left: calc(100% - 200px); + top: 0; +} + +.anchor-wrp { + width: calc(100% - 200px); + display: inline-block; + height: 0; + position: relative; + left: 0; + top: 0; +} diff --git a/src/app/components/shared/view-comment-data/view-comment-data.component.ts b/src/app/components/shared/view-comment-data/view-comment-data.component.ts index 9098e0ddda94e15cc0826e0840e3ed7a7439c72d..1b570f892e530a93c2105f52d88e9dd4f6fee4d5 100644 --- a/src/app/components/shared/view-comment-data/view-comment-data.component.ts +++ b/src/app/components/shared/view-comment-data/view-comment-data.component.ts @@ -1,4 +1,11 @@ -import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + Input, + OnInit, + ViewChild, +} from '@angular/core'; import { QuillEditorComponent, QuillModules, QuillViewComponent } from 'ngx-quill'; import Delta from 'quill-delta'; import Quill from 'quill'; @@ -14,15 +21,6 @@ import { LanguagetoolResult } from '../../../services/http/languagetool.service' Quill.register('modules/imageResize', ImageResize); -const participantToolbar = [ - ['bold', { list: 'bullet' }, { list: 'ordered' }, 'blockquote', 'link', 'code-block', 'formula', 'emoji'] -]; - -const moderatorToolbar = [ - ['bold', { color: [] }, 'strike', { list: 'bullet' }, { list: 'ordered' }, 'blockquote', - 'link', 'image', 'video', 'code-block', 'formula', 'emoji'], -]; - @Component({ selector: 'app-view-comment-data', templateUrl: './view-comment-data.component.html', @@ -53,18 +51,12 @@ export class ViewCommentDataComponent implements OnInit, AfterViewInit { @Input() maxDataCharacters = 1500; @Input() placeHolderText = ''; @Input() afterEditorInit?: () => void; + @Input() usesFormality = false; + @Input() formalityEmitter: (string) => void; + @Input() selectedFormality = 'default'; currentText = '\n'; - quillModules: QuillModules = { - toolbar: { - container: participantToolbar, - handlers: { - image: () => this.handle('image'), - video: () => this.handle('video'), - link: () => this.handle('link'), - formula: () => this.handle('formula') - } - } - }; + quillModules: QuillModules = {}; + hasEmoji = true; private _currentData = null; private _marks: Marks; @@ -118,9 +110,6 @@ export class ViewCommentDataComponent implements OnInit, AfterViewInit { ngOnInit(): void { - if (this.isModerator) { - this.quillModules.toolbar['container'] = moderatorToolbar; - } const isMobile = this.deviceInfo.isUserAgentMobile; if (this.isEditor) { this.quillModules['emoji-toolbar'] = !isMobile; @@ -128,6 +117,7 @@ export class ViewCommentDataComponent implements OnInit, AfterViewInit { this.quillModules.imageResize = { modules: ['Resize', 'DisplaySize'] }; + this.hasEmoji = !isMobile; } this.translateService.use(localStorage.getItem('currentLang')); if (this.isEditor) { @@ -242,9 +232,16 @@ export class ViewCommentDataComponent implements OnInit, AfterViewInit { this._marks.copy(viewCommentData._marks); } + public onClick(e: MouseEvent, type) { + e.preventDefault(); + e.stopImmediatePropagation(); + this.handle(type); + return false; + } + private overrideQuillTooltip() { const tooltip = this.editor.quillEditor.theme.tooltip; - const prev = tooltip.show; + const prev = tooltip.show.bind(tooltip); let range; tooltip.show = () => { const sel = this.editor.quillEditor.getSelection(false); @@ -263,7 +260,7 @@ export class ViewCommentDataComponent implements OnInit, AfterViewInit { currentSize += 1; } } - prev.bind(tooltip)(); + prev(); }; tooltip.edit = (type: string, value: string) => { this.handle(type, value, (val: string) => { diff --git a/src/app/components/shared/write-comment/write-comment.component.ts b/src/app/components/shared/write-comment/write-comment.component.ts index e0aaf9bd6535cf53598225584f45663d8f9b6507..66ec29e4ba6468c9ead49761b6afccac575a350c 100644 --- a/src/app/components/shared/write-comment/write-comment.component.ts +++ b/src/app/components/shared/write-comment/write-comment.component.ts @@ -7,8 +7,6 @@ import { EventService } from '../../../services/util/event.service'; import { LanguageService } from '../../../services/util/language.service'; import { ViewCommentDataComponent } from '../view-comment-data/view-comment-data.component'; import { DeepLService, SourceLang, TargetLang } from '../../../services/http/deep-l.service'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; import { DeepLDialogComponent } from '../_dialogs/deep-ldialog/deep-ldialog.component'; import { MatDialog } from '@angular/material/dialog'; @@ -51,8 +49,7 @@ export class WriteCommentComponent implements OnInit { public eventService: EventService, public languagetoolService: LanguagetoolService, private deeplService: DeepLService, - private dialog: MatDialog, - public deepl: DeepLService) { + private dialog: MatDialog) { this.languageService.langEmitter.subscribe(lang => { this.translateService.use(lang); }); @@ -84,22 +81,6 @@ export class WriteCommentComponent implements OnInit { return true; } - private static encodeHTML(str: string): string { - return str.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - private static decodeHTML(str: string): string { - return str.replace(/'/g, '\'') - .replace(/"/g, '"') - .replace(/>/g, '>') - .replace(/</g, '<') - .replace(/&/g, '&'); - } - ngOnInit(): void { this.translateService.use(localStorage.getItem('currentLang')); if (this.isCommentAnswer) { @@ -183,56 +164,43 @@ export class WriteCommentComponent implements OnInit { result: LanguagetoolResult, onClose: (data: string, text: string, view: ViewCommentDataComponent) => void) { let target = TargetLang.EN_US; - if (result.language.detectedLanguage.code.toUpperCase().startsWith(SourceLang.EN)) { + const code = result.language.detectedLanguage.code.toUpperCase().split('-')[0]; + const source = code in SourceLang ? SourceLang[code] : null; + if (code.startsWith(SourceLang.EN)) { target = TargetLang.DE; } - this.generateDeeplDelta(body, target).subscribe(([improvedBody, improvedText]) => { - this.isSpellchecking = false; - if (improvedText.replace(/\s+/g, '') === text.replace(/\s+/g, '')) { - onClose(body, text, this.commentData); - return; - } - this.dialog.open(DeepLDialogComponent, { - width: '900px', - maxWidth: '100%', - data: { - body, - text, - improvedBody, - improvedText, - maxTextCharacters: this.maxTextCharacters, - maxDataCharacters: this.maxDataCharacters, - isModerator: this.isModerator, - result, - onClose - } - }).afterClosed().subscribe(() => this.buildCreateCommentActionCallback()()); - }, (_) => { - this.isSpellchecking = false; - onClose(body, text, this.commentData); - }); - } - - private generateDeeplDelta(body: string, targetLang: TargetLang): Observable<[string, string]> { - const delta = ViewCommentDataComponent.getDeltaFromData(body); - const xml = delta.ops.reduce((acc, e, i) => { - if (typeof e['insert'] === 'string' && e['insert'].trim().length) { - acc += '<x i="' + i + '">' + WriteCommentComponent.encodeHTML(e['insert']) + '</x>'; - e['insert'] = ''; - } - return acc; - }, ''); - return this.deeplService.improveTextStyle(xml, targetLang).pipe( - map(str => { - const regex = /<x i="(\d+)">([^<]+)<\/x>/gm; - let m; - while ((m = regex.exec(str)) !== null) { - delta.ops[+m[1]]['insert'] += WriteCommentComponent.decodeHTML(m[2]); + DeepLDialogComponent.generateDeeplDelta(this.deeplService, body, target) + .subscribe(([improvedBody, improvedText]) => { + this.isSpellchecking = false; + if (improvedText.replace(/\s+/g, '') === text.replace(/\s+/g, '')) { + onClose(body, text, this.commentData); + return; } - const text = delta.ops.reduce((acc, el) => acc + (typeof el['insert'] === 'string' ? el['insert'] : ''), ''); - return [ViewCommentDataComponent.getDataFromDelta(delta), text]; - }) - ); + this.dialog.open(DeepLDialogComponent, { + width: '900px', + maxWidth: '100%', + data: { + body, + text, + improvedBody, + improvedText, + maxTextCharacters: this.maxTextCharacters, + maxDataCharacters: this.maxDataCharacters, + isModerator: this.isModerator, + result, + onClose, + target: DeepLService.transformSourceToTarget(source), + usedTarget: target + } + }).afterClosed().subscribe((val) => { + if (val) { + this.buildCreateCommentActionCallback()(); + } + }); + }, (_) => { + this.isSpellchecking = false; + onClose(body, text, this.commentData); + }); } } diff --git a/src/app/directives/joyride-template.directive.ts b/src/app/directives/joyride-template.directive.ts index 78b18a019cc10ce6d24ddeff70a8ae003d94af00..63507799fa8f915279d52ae69ce8b3b477c87ed5 100644 --- a/src/app/directives/joyride-template.directive.ts +++ b/src/app/directives/joyride-template.directive.ts @@ -1,7 +1,6 @@ import { ComponentFactoryResolver, Directive, - Input, OnInit, ViewContainerRef } from '@angular/core'; diff --git a/src/app/services/http/deep-l.service.ts b/src/app/services/http/deep-l.service.ts index 1462c9f5349895432b7b4b14180b5f0fd814f2e1..499c71f22726463fa3735f9ea040cd6dfcba1168 100644 --- a/src/app/services/http/deep-l.service.ts +++ b/src/app/services/http/deep-l.service.ts @@ -4,7 +4,6 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { catchError, map, tap, timeout } from 'rxjs/operators'; import { flatMap } from 'rxjs/internal/operators'; -import { LanguagetoolService } from './languagetool.service'; const httpOptions = { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -75,6 +74,12 @@ export enum TargetLang { ZH = 'ZH' } +export enum FormalityType { + default = '', + less = 'less', + more = 'more' +} + @Injectable({ providedIn: 'root' }) @@ -112,22 +117,23 @@ export class DeepLService extends BaseHttpService { } } - improveTextStyle(text: string, temTargetLang: TargetLang): Observable<string> { - return this.makeXMLTranslateRequest(text, temTargetLang).pipe( + improveTextStyle(text: string, temTargetLang: TargetLang, formality = FormalityType.default): Observable<string> { + return this.makeXMLTranslateRequest(text, temTargetLang, formality).pipe( flatMap(result => this.makeXMLTranslateRequest( result.translations[0].text, - DeepLService.transformSourceToTarget(result.translations[0].detected_source_language) + DeepLService.transformSourceToTarget(result.translations[0].detected_source_language), + formality )), map(result => result.translations[0].text) ); } - private makeXMLTranslateRequest(text: string, targetLang: TargetLang): Observable<DeepLResult> { + private makeXMLTranslateRequest(text: string, targetLang: TargetLang, formality: FormalityType): Observable<DeepLResult> { const url = '/deepl/translate'; - const formality = DeepLService.supportsFormality(targetLang) ? '&formality=less' : ''; + const tagFormality = DeepLService.supportsFormality(targetLang) && formality !== FormalityType.default ? '&formality=' + formality : ''; const additional = '?target_lang=' + encodeURIComponent(targetLang) + - '&tag_handling=xml' + formality + + '&tag_handling=xml' + tagFormality + '&text=' + encodeURIComponent(text); return this.http.get<string>(url + additional, httpOptions) .pipe( diff --git a/src/app/services/util/tag-cloud-data.service.ts b/src/app/services/util/tag-cloud-data.service.ts index 477d1240125b504f45fbfce400634b05ee5d5919..f0832a5d0d1fbe4c7e28115e1184e9b8f90896cd 100644 --- a/src/app/services/util/tag-cloud-data.service.ts +++ b/src/app/services/util/tag-cloud-data.service.ts @@ -23,6 +23,9 @@ export interface TagCloudDataTagEntry { categories: Set<string>; dependencies: Set<string>; comments: Comment[]; + generatedByQuestionerCount: number; + taggedCommentsCount: number; + answeredCommentsCount: number; } export interface TagCloudMetaData { @@ -99,43 +102,50 @@ 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: SpacyKeyword) => { - let current: TagCloudDataTagEntry = data.get(keyword.lemma); - const commentDate = new Date(comment.timestamp); - if (current === undefined) { - current = { - cachedVoteCount: 0, - cachedUpVotes: 0, - cachedDownVotes: 0, - comments: [], - weight: 0, - adjustedWeight: 0, - distinctUsers: new Set<number>(), - categories: new Set<string>(), - dependencies: new Set<string>([...keyword.dep]), - firstTimeStamp: commentDate, - lastTimeStamp: commentDate - }; - 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; - current.distinctUsers.add(comment.userNumber); - if (comment.tag) { - current.categories.add(comment.tag); - } - // @ts-ignore - if (current.firstTimeStamp - commentDate > 0) { - current.firstTimeStamp = commentDate; - } - // @ts-ignore - if (current.lastTimeStamp - commentDate < 0) { - current.lastTimeStamp = commentDate; - } - current.comments.push(comment); - }); + TopicCloudAdminService.approveKeywordsOfComment(comment, adminData, + (keyword: SpacyKeyword, isFromQuestioner: boolean) => { + let current: TagCloudDataTagEntry = data.get(keyword.lemma); + const commentDate = new Date(comment.timestamp); + if (current === undefined) { + current = { + cachedVoteCount: 0, + cachedUpVotes: 0, + cachedDownVotes: 0, + comments: [], + weight: 0, + adjustedWeight: 0, + distinctUsers: new Set<number>(), + categories: new Set<string>(), + dependencies: new Set<string>([...keyword.dep]), + firstTimeStamp: commentDate, + lastTimeStamp: commentDate, + generatedByQuestionerCount: 0, + taggedCommentsCount: 0, + answeredCommentsCount: 0 + }; + 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; + current.distinctUsers.add(comment.userNumber); + current.generatedByQuestionerCount += +isFromQuestioner; + current.taggedCommentsCount += +!!comment.tag; + current.answeredCommentsCount += +!!comment.answer; + if (comment.tag) { + current.categories.add(comment.tag); + } + // @ts-ignore + if (current.firstTimeStamp - commentDate > 0) { + current.firstTimeStamp = commentDate; + } + // @ts-ignore + if (current.lastTimeStamp - commentDate < 0) { + current.lastTimeStamp = commentDate; + } + current.comments.push(comment); + }); users.add(comment.userNumber); } return [ @@ -199,7 +209,10 @@ export class TagCloudDataService { distinctUsers: new Set<number>(), dependencies: new Set<string>(), firstTimeStamp: new Date(), - lastTimeStamp: new Date() + lastTimeStamp: new Date(), + generatedByQuestionerCount: 0, + taggedCommentsCount: 0, + answeredCommentsCount: 0 }); } }); @@ -317,13 +330,19 @@ export class TagCloudDataService { } private calculateWeight(tagData: TagCloudDataTagEntry): number { + const value = Math.max(tagData.cachedVoteCount, 0); + const additional = (tagData.distinctUsers.size - 1) * 0.5 + + tagData.comments.reduce((acc, comment) => acc + +comment.createdFromLecturer, 0) + + tagData.generatedByQuestionerCount + + tagData.taggedCommentsCount + + tagData.answeredCommentsCount; switch (this._calcWeightType) { case TagCloudCalcWeightType.byVotes: - return tagData.cachedVoteCount; + return value + additional; case TagCloudCalcWeightType.byLengthAndVotes: - return tagData.cachedVoteCount / 10.0 + tagData.comments.length; + return value / 10.0 + tagData.comments.length + additional; default: - return tagData.comments.length; + return tagData.comments.length + additional; } } diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index ab6ec2b2fa517b53da2d357b7879fa7e157554bf..741de7eb7cbf14d002d714da5dea98c6219dda41 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -52,11 +52,16 @@ export class TopicCloudAdminService { room.tagCloudSettings = JSON.stringify(settings); } - static approveKeywordsOfComment(comment: Comment, config: TopicCloudAdminData, keywordFunc: (SpacyKeyword) => void) { + static approveKeywordsOfComment(comment: Comment, config: TopicCloudAdminData, keywordFunc: (SpacyKeyword, boolean) => void) { let source = comment.keywordsFromQuestioner; + let isFromQuestioner = true; if (config.keywordORfulltext === KeywordOrFulltext.both) { - source = !source || !source.length ? comment.keywordsFromSpacy : source; + if (!source || !source.length) { + source = comment.keywordsFromSpacy; + isFromQuestioner = false; + } } else if (config.keywordORfulltext === KeywordOrFulltext.fulltext) { + isFromQuestioner = false; source = comment.keywordsFromSpacy; } if (!source) { @@ -76,7 +81,7 @@ export class TopicCloudAdminService { } } if (!isProfanity) { - keywordFunc(keyword); + keywordFunc(keyword, isFromQuestioner); } } } diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index 32db47992e0fbee0f7dbb906a5d5bde0aef1a8e9..37451b2f311611931797216d63e63fb8e30b1c74 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -242,6 +242,13 @@ "option-normal": "Deine Eingabe:", "option-improved": "KI-Vorschlag:" }, + "deepl-formality-select": { + "error": "Text konnte nicht geupdatet werden.", + "name": "Formalität", + "default": "Automatisch", + "less": "Informal", + "more": "Formal" + }, "explanation": { "label": "Warum?", "close": "Schließen", diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index dd1d0b93855914e5a8adeae2d812d9b10bdb9f90..191697323754111fd46da8b653949ddc6b18d944 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -243,6 +243,13 @@ "option-normal": "Your input:", "option-improved": "AI suggestion:" }, + "deepl-formality-select": { + "error": "Could not update Text.", + "name": "Formality", + "default": "Automatically", + "less": "Informally", + "more": "Formally" + }, "explanation": { "label": "Explanation", "close": "Close", diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index e11cd3b7b8c078001112ad6e8ca6c8b6b482125e..0519cea628e21cee7a48269dae3aa15dbd6b9482 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -170,6 +170,13 @@ "option-normal": "Deine Eingabe:", "option-improved": "KI-Vorschlag:" }, + "deepl-formality-select": { + "error": "Text konnte nicht geupdatet werden.", + "name": "Formalität", + "default": "Automatisch", + "less": "Informal", + "more": "Formal" + }, "explanation": { "label": "Warum?", "close": "Schließen", @@ -285,7 +292,7 @@ "demo-data-topic": "Thema %d", "overview-question-topic-tooltip": "Anzahl Fragen mit diesem Thema", "overview-questioners-topic-tooltip": "Anzahl Fragensteller*innen mit diesem Thema", - "period-since-first-comment":"Zeitraum seit der ersten Frage", + "period-since-first-comment": "Zeitraum seit der ersten Frage", "upvote-topic": "Up-Votes für dieses Thema", "downvote-topic": "Down-Votes für dieses Thema", "blacklist-topic": "Thema auf die »Blacklist« setzen", @@ -417,12 +424,12 @@ "highestWeight-tooltip": "x Themen mit der höchsten Gewichtung anzeigen", "rotate-weight": "Themen dieser Häufigkeitsgruppe um x Grad drehen", "rotate-weight-tooltip": "Themen dieser Häufigkeitsgruppe um x Grad drehen", - "font":"Schrift", + "font": "Schrift", "reset-btn": "Auf Standardwerte setzen", "font-family-tooltip": "Schrift auswählen …", "bold-notation-tooltip": "Schrift fett setzen", - "font-style-bold" : "Fette Schrift", - "font-family":"Schriftart", + "font-style-bold": "Fette Schrift", + "font-family": "Schriftart", "manual-weight-number": "Anzahl Themen beschränken", "manual-weight-number-tooltip": "Anzahl Themen der Häufigkeitsgruppe", "manual-weight-number-note": "Begrenzt die Anzahl Themen einer Häufigkeitsgruppe auf den eingestellten Wert" diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 4cb91c15a5418c830290019748062dc332a3e49f..abbee0a6f8f869a5b53609dd7fb3ee8e7f24f5ca 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -179,6 +179,13 @@ "option-normal": "Your input:", "option-improved": "AI suggestion:" }, + "deepl-formality-select": { + "error": "Could not update Text.", + "name": "Formality", + "default": "Automatically", + "less": "Informally", + "more": "Formally" + }, "explanation": { "label": "Explanation", "close": "Close",