diff --git a/src/app/components/shared/_dialogs/create-comment/create-comment.component.html b/src/app/components/shared/_dialogs/create-comment/create-comment.component.html index d052cb60f18109b9315286557ca552be6fc712a8..eb4b8d88af2d0af963d0e30a5ac2b763dfb28580 100644 --- a/src/app/components/shared/_dialogs/create-comment/create-comment.component.html +++ b/src/app/components/shared/_dialogs/create-comment/create-comment.component.html @@ -1,7 +1,9 @@ <ars-row ars-flex-box> <ars-row> <div class="lang-selection"> - <button class="lang-btn" mat-button (click)="select.open()"> + <button class="lang-btn" mat-button (click)="select.open()" + matTooltip="{{ 'spacy-dialog.lang-button-hint' | translate }}" + matTooltipShowDelay="750"> <i class="material-icons">language</i> {{'spacy-dialog.' + (selectedLang === 'auto' ? 'auto' : languagetoolService.mapLanguageToSpacyModel(selectedLang)) | translate}} <mat-select class="select-list" #select [(ngModel)]="selectedLang"> @@ -40,12 +42,12 @@ <mat-divider></mat-divider> </ars-row> <ars-row [height]="12"></ars-row> - <ars-row [overflow]="'auto'" - style="max-height:calc( 100vh - 250px )"> + <ars-row [overflow]="'visible'" style="max-height:calc( 100vh - 250px )"> <mat-form-field style="width:100%;"> <input [disabled]="true" matInput> <div [contentEditable]="true" [spellcheck]="false" + spellcheck="false" (focus)="eventService.makeFocusOnInputTrue()" style="margin-top:15px;width:100%;" (blur)="eventService.makeFocusOnInputFalse()" @@ -65,7 +67,7 @@ </mat-hint> <mat-hint align="end"> <span aria-hidden="true"> - {{commentBody.innerText.length}} / {{user.role === 3 ? 1000 : 500}} + {{inputText.length}} / {{user.role === 3 ? 1000 : 500}} </span> </mat-hint> </mat-form-field> @@ -89,10 +91,10 @@ <mat-divider></mat-divider> </ars-row> <ars-row ars-flex-box> - <ars-fill> - </ars-fill> <ars-col> - <button (click)="grammarCheck(commentBody)">{{ 'comment-page.grammar-check' | translate}}</button> + <button class="mat-flat-button spell-button" (click)="grammarCheck(commentBody)">{{ 'comment-page.grammar-check' | translate}}</button> + </ars-col> + <ars-col> <app-dialog-action-buttons buttonsLabelSection="comment-page" confirmButtonLabel="send" diff --git a/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss b/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss index 29c6198bbf2586f39398bbbcc98b05c642293855..41d5b82dbc9498fee7f9f9c119a673d3708c554a 100644 --- a/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss +++ b/src/app/components/shared/_dialogs/create-comment/create-comment.component.scss @@ -26,6 +26,11 @@ app-comment-list { outline: none; } } +.spell-button{ + background-color: var(--primary); + color: var(--on-primary); + margin-top: 1rem; +} .send { color: var(--on-primary); diff --git a/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts b/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts index 801608786cba8f82a276509e1a557aeb0d73b933..cb679c21af613760a779abe073e0d26a80fa9718 100644 --- a/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts +++ b/src/app/components/shared/_dialogs/create-comment/create-comment.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Comment } from '../../../../models/comment'; import { NotificationService } from '../../../../services/util/notification.service'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -15,7 +15,7 @@ import { LanguagetoolService, Language } from '../../../../services/http/languag templateUrl: './create-comment.component.html', styleUrls: ['./create-comment.component.scss'] }) -export class CreateCommentComponent implements OnInit { +export class CreateCommentComponent implements OnInit, OnDestroy { comment: Comment; @@ -23,6 +23,7 @@ export class CreateCommentComponent implements OnInit { roomId: string; tags: string[]; selectedTag: string; + inputText = ''; body: string; languages: Language[] = ['de-DE', 'en-US', 'fr', 'auto']; @@ -30,7 +31,7 @@ export class CreateCommentComponent implements OnInit { bodyForm = new FormControl('', [Validators.required]); - @ViewChild('commentBody', { static: true })commentBody: HTMLDivElement; + @ViewChild('commentBody', { static: true }) commentBody: HTMLDivElement; constructor( private notification: NotificationService, @@ -47,9 +48,24 @@ export class CreateCommentComponent implements OnInit { this.translateService.use(localStorage.getItem('currentLang')); setTimeout(() => { document.getElementById('answer-input').focus(); + document.addEventListener('click', this.onDocumentClick); }, 0); } + onDocumentClick(e) { + const container = document.getElementsByClassName('dropdownBlock'); + Array.prototype.forEach.call(container, (elem) => { + if (!elem.contains(e.target) && (!(e.target as Node).parentElement.classList.contains('markUp') + || (e.target as HTMLElement).dataset.id !== ((elem as Node).parentElement as HTMLElement).dataset.id)) { + (elem as HTMLElement).style.display = 'none'; + } + }); + } + + ngOnDestroy() { + document.removeEventListener('click', this.onDocumentClick); + } + onNoClick(): void { this.dialogRef.close(); } @@ -78,27 +94,36 @@ export class CreateCommentComponent implements OnInit { } openSpacyDialog(comment: Comment): void { - this.checkSpellings(comment.body).subscribe((res) => { - let commentBodyChecked = comment.body; - const commentLang = this.languagetoolService.mapLanguageToSpacyModel(res.language.code); - for(let i = res.matches.length - 1; i >= 0; i--){ - commentBodyChecked = commentBodyChecked.substr(0, res.matches[i].offset) + - commentBodyChecked.substr(res.matches[i].offset + res.matches[i].length, commentBodyChecked.length); - } - const dialogRef = this.dialog.open(SpacyDialogComponent, { - data: { - comment, - commentLang, - commentBodyChecked + this.checkSpellings(this.inputText).subscribe((res) => { + const words: string[] = this.inputText.trim().split(' '); + const errorQuotient = (res.matches.length * 100) / words.length; + + if (errorQuotient <= 20) { + let commentBodyChecked = this.inputText; + const commentLang = this.languagetoolService.mapLanguageToSpacyModel(res.language.code); + + for (let i = res.matches.length - 1; i >= 0; i--) { + commentBodyChecked = commentBodyChecked.substr(0, res.matches[i].offset) + + commentBodyChecked.substr(res.matches[i].offset + res.matches[i].length, commentBodyChecked.length); } - }); - dialogRef.afterClosed() - .subscribe(result => { - if (result) { - this.dialogRef.close(result); + const dialogRef = this.dialog.open(SpacyDialogComponent, { + data: { + comment, + commentLang, + commentBodyChecked } }); + + dialogRef.afterClosed() + .subscribe(result => { + if (result) { + this.dialogRef.close(result); + } + }); + } else { + this.dialogRef.close(comment); + } }); }; @@ -123,41 +148,79 @@ export class CreateCommentComponent implements OnInit { } maxLength(commentBody: HTMLDivElement): void { - // Cut the text down to 500 or 1000 chars depending on the user role. - if(this.user.role === 3 && commentBody.innerText.length > 1000) { + this.inputText = commentBody.innerText; + if (this.user.role === 3 && commentBody.innerText.length > 1000) { commentBody.innerText = commentBody.innerText.slice(0, 1000); - } else if(this.user.role !== 3 && commentBody.innerText.length > 500){ + } else if (this.user.role !== 3 && commentBody.innerText.length > 500) { commentBody.innerText = commentBody.innerText.slice(0, 500); } this.body = commentBody.innerText; } grammarCheck(commentBody: HTMLDivElement): void { - let wrongWords: string[] = []; + const wrongWords: string[] = []; + commentBody.innerHTML = this.inputText; this.checkSpellings(commentBody.innerText).subscribe((wordsCheck) => { - if(wordsCheck.matches.length > 0 ) { + if (wordsCheck.matches.length > 0) { wordsCheck.matches.forEach(grammarError => { const wrongWord = commentBody.innerText.slice(grammarError.offset, grammarError.offset + grammarError.length); wrongWords.push(wrongWord); }); + this.checkSpellings(commentBody.innerHTML).subscribe((res) => { - for(let i = res.matches.length - 1; i >= 0; i--){ // Reverse for loop to make sure the offset is right. + for (let i = res.matches.length - 1; i >= 0; i--) { const wrongWord = commentBody.innerHTML .slice(res.matches[i].offset, res.matches[i].offset + res.matches[i].length); - if (wrongWords.includes(wrongWord)) { // Only replace the real Words, excluding the HTML tags - const msg = res.matches[i].message; // The explanation of the suggestion for improvement - const suggestions = res.matches[i].replacements; // The suggestions for improvement. Access: suggestions[x].value - const replacement = '<span style="text-decoration: underline wavy red">' // set the Styling for all marked words - // Select menu with suggestions has to be injected here. - + wrongWord + - '</span>'; + if (wrongWords.includes(wrongWord)) { + const suggestions: any[] = res.matches[i].replacements; + let displayOptions = 3; + let suggestionsHTML = ''; + + if (!suggestions.length) { + suggestionsHTML = '<span style="color: black; display: block; text-align: center;">' + res.matches[i].message + '</span>'; + } + + if (suggestions.length < displayOptions) { + displayOptions = suggestions.length; + } + + for (let j = 0; j < displayOptions; j++) { + // eslint-disable-next-line max-len + suggestionsHTML += '<span class="suggestions"' + ' style="color: black; display: block; text-align: center; cursor: pointer;">' + suggestions[j].value + '</span>'; + } + + const replacement = + '<div class="markUp" data-id="'+i+'" style="position: relative; display: inline-block; border-bottom: 1px dotted black">' + + ' <span data-id="' + i + '" style="text-decoration: underline wavy red; cursor: pointer;">' + + wrongWord + + ' </span>' + + // eslint-disable-next-line max-len + ' <div class="dropdownBlock" style="display: none; width: 160px; background-color: white; border-style: solid; border-color: var(--primary); color: #fff; text-align: center; border-radius: 6px; padding: 5px 0; position: absolute; z-index: 1000; bottom: 100%; left: 50%; margin-left: -80px;">' + + suggestionsHTML + + ' </div>' + + '</div>'; + commentBody.innerHTML = commentBody.innerHTML.substr(0, res.matches[i].offset) + - replacement + - commentBody.innerHTML.substr(res.matches[i].offset + wrongWord.length, - commentBody.innerHTML.length); + replacement + commentBody.innerHTML.substr(res.matches[i].offset + wrongWord.length, commentBody.innerHTML.length); } } + + setTimeout(() => { + Array.from(document.getElementsByClassName('markUp')).forEach(marked => { + marked.addEventListener('click', () => { + ((marked as HTMLElement).lastChild as HTMLElement).style.display = 'block'; + setTimeout(() => { + Array.from(document.getElementsByClassName('suggestions')).forEach(e => { + e.addEventListener('click', () => { + e.parentElement.parentElement.outerHTML = e.innerHTML; + this.inputText = commentBody.innerText; + }); + }); + }, 500); + }); + }); + }, 500); }); } }); diff --git a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html index 6f7dfe97e96d9aaf5c3e8865b3132239acc01fd2..3a2cab8045ff60eeb87392acaf297dbc199f95ce 100644 --- a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html +++ b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.html @@ -2,8 +2,19 @@ <div class="anchor-wrp"> <span *ngIf="keywords.length > 0"> <ars-row class="select-all-section"> - <mat-checkbox class="select-all-checkbox" id="checkAll" (change)="selectAll(checkall.checked)" #checkall></mat-checkbox> - <mat-label class="select-all-label" for="checkAll" (click)="checkall.checked = !checkall.checked; selectAll(checkall.checked)"> + <mat-checkbox class="select-all-checkbox" + id="checkAll" + (change)="selectAll(checkall.checked)" + #checkall + matTooltip="{{ 'spacy-dialog.select-all-hint' | translate }}" + matTooltipShowDelay="750"> + </mat-checkbox> + <mat-label class="select-all-label" + for="checkAll" + (click)="checkall.checked = !checkall.checked; + selectAll(checkall.checked)" + matTooltip="{{ 'spacy-dialog.select-all-hint' | translate }}" + matTooltipShowDelay="750"> <mat-icon class="select-all-icon">playlist_add_check</mat-icon> {{ 'spacy-dialog.select-all' | translate }} </mat-label> @@ -22,16 +33,22 @@ <div class="keywords-actions"> <mat-checkbox [checked]="keyword.completed" (change)="keyword.selected = $event.checked" - [(ngModel)]="keyword.completed"> + [(ngModel)]="keyword.completed" + matTooltip="{{ 'spacy-dialog.select-keyword-hint' | translate }}" + matTooltipShowDelay="750"> </mat-checkbox> <button *ngIf="!keyword.editing" (click)="onEdit(keyword)" mat-icon-button - [ngClass]="{'keywords-actions-selected': keyword.selected}"> + [ngClass]="{'keywords-actions-selected': keyword.selected}" + matTooltip="{{ 'spacy-dialog.edit-keyword-hint' | translate }}" + matTooltipShowDelay="750"> <mat-icon>edit</mat-icon> </button> <button *ngIf="keyword.editing" (click)="onEndEditing(keyword)" mat-icon-button - class = "edit-accept"> + class = "edit-accept" + matTooltip="{{ 'spacy-dialog.editing-done-hint' | translate }}" + matTooltipShowDelay="750"> <mat-icon>check</mat-icon> </button> </div> diff --git a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss index 0185073a2871ecd5831c192434264b6b38a4b5da..8fc238f37097e3c64d50f079b433f42f1e91bac7 100644 --- a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss +++ b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.scss @@ -3,7 +3,7 @@ height: 40px !important; } .keywords-list { - height: 343px; + max-height: 340px; flex-grow: 1; flex-shrink: 1; overflow: auto; diff --git a/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.ts b/src/app/components/shared/_dialogs/spacy-dialog/spacy-dialog.component.ts index de9d06bf9d05db565bec65df1e546974972f28e6..43d7c0dd137e299aa7099055fe0de5b4fef000e5 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 @@ -23,8 +23,8 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { comment: Comment; commentLang: Model; commentBodyChecked: string; - spacyKeywords: string[] = []; keywords: Keyword[] = []; + keywordsOriginal: Keyword[] = []; constructor( protected langService: LanguageService, @@ -53,30 +53,43 @@ export class SpacyDialogComponent implements OnInit, AfterContentInit { buildCreateCommentActionCallback() { return () => { this.comment.keywordsFromQuestioner = this.keywords.filter(kw => kw.selected).map(kw => kw.word); - this.comment.keywordsFromSpacy = this.spacyKeywords; + this.comment.keywordsFromSpacy = this.keywordsOriginal.map(kw => kw.word); this.dialogRef.close(this.comment); }; } evalInput(model: Model) { - const words: Keyword[] = []; + const keywords: Keyword[] = []; + let regex; + if(this.commentLang === 'de') { + regex = new RegExp('(?!Der|Die|Das)[A-ZAÄÖÜ][a-zäöüß]+(-[A-Z][a-zäöüß]+)*', 'g'); + } else if (this.commentLang === 'en') { + regex = new RegExp('(?!he|she|it|for|with)[a-z]{2,}(-[a-z]{2,})*', 'gi'); + } else { + regex = new RegExp('(?!au|de|la|le|en|un)[A-ZÀ-Ÿ]{2,}', 'gi'); + } // N at first pos = all Nouns(NN de/en) including singular(NN, NNP en), plural (NNPS, NNS en), proper Noun(NNE, NE de) - this.spacyService.getKeywords(this.commentBodyChecked, model).subscribe(res => { - this.spacyKeywords = res; - const wordsArr = []; - for (const tag of res) { - wordsArr.push({ - word: tag, - completed: false, - editing: false, - selected: false - }); - } - this.keywords = wordsArr; - }, () => { - this.spacyKeywords = []; - this.keywords = []; - }); + this.spacyService.getKeywords(this.commentBodyChecked, model) + .subscribe(words => { + for(const word of words) { + const filteredwords = word.match(regex); + for (const filteredword of filteredwords) { + if(filteredword !== null && filteredword !== undefined && keywords.filter(item => item.word === filteredword).length < 1) { + keywords.push({ + word: filteredword, + completed: false, + editing: false, + selected: false + }); + } + } + } + this.keywords = keywords; + this.keywordsOriginal = keywords; + }, () => { + this.keywords = []; + this.keywordsOriginal = []; + }); } onEdit(keyword) { diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index fdee800adcf2f7b2a9d15dcb3994b2974c33ad84..92d6e9d3f34fd180d1e7293f0b20b47219fd7a09 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -88,7 +88,12 @@ "en": "Englisch", "fr": "Französisch", "empty-nouns": "Keine Nomen enthalten", - "select-all": "Alles auswählen" + "select-all": "Alles auswählen", + "lang-button-hint": "Ausgewählte Sprache für die Rechtschreibprüfung", + "select-all-hint": "Alle Stichwörter auswählen", + "select-keyword-hint": "Dieses Stickwort auswählen", + "edit-keyword-hint": "Stichwort editieren", + "editing-done-hint": "Editierung abschliessen" }, "comment-page": { "a11y-comment_delete": "Löscht diese Frage", diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index 94f74155d652f514048758b322e62c86835e5cad..f5de170d327d0ac227f7b6b01d4fa8d3468ca384 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -89,7 +89,12 @@ "en": "English", "fr": "French", "empty-nouns": "No nouns included", - "select-all": "Select all" + "select-all": "Select all", + "lang-button-hint": "Selected language for spell check", + "select-all-hint": "Select all keywords", + "select-keyword-hint": "Select this keyword", + "edit-keyword-hint": "Edit keyword", + "editing-done-hint": "Finish editing" }, "comment-page": { "a11y-comment_delete": "Deletes this question", diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 2e2cbf6e70311e9a35b63573be254bb03fbc3968..091346d02c1be5d515ca8c18d48f8aa5b7c0b1e3 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -93,7 +93,12 @@ "en": "Englisch", "fr": "Französisch", "empty-nouns": "Keine Nomen enthalten", - "select-all": "Alles auswählen" + "select-all": "Alles auswählen", + "lang-button-hint": "Ausgewählte Sprache für die Rechtschreibprüfung", + "select-all-hint": "Alle Stichwörter auswählen", + "select-keyword-hint": "Dieses Stickwort auswählen", + "edit-keyword-hint": "Stichwort editieren", + "editing-done-hint": "Editierung abschliessen" }, "comment-page": { "a11y-comment_input": "Gib deine Frage ein", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 8ec083e376d342e82786144bfd1447b57c48b152..70807cdae5353ef4048ff202b5ab188db68e62c8 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -103,7 +103,12 @@ "en": "English", "fr": "French", "empty-nouns": "No nouns included", - "select-all": "Select all" + "select-all": "Select all", + "lang-button-hint": "Selected language for spell check", + "select-all-hint": "Select all keywords", + "select-keyword-hint": "Select this keyword", + "edit-keyword-hint": "Edit keyword", + "editing-done-hint": "Finish editing" }, "comment-page": { "a11y-comment_input": "Enter your question",