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 0347f4a1c2c13172c71a48055975ddc19f33d6db..744beea27e2131ebac46c0fa000c3a5f410b0f75 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,144 +1,9 @@ <ars-row ars-flex-box> - <ars-row> - <div class="lang-selection"> - <button class="lang-btn" - mat-button - (click)="select.open()" - matTooltip="{{ 'spacy-dialog.lang-button-hint' | translate }}" - matTooltipShowDelay="750"> - <i class="material-icons">language</i> - <span *ngIf="!(grammarChecker.selectedLang === 'auto')"> - {{'spacy-dialog.' + (languagetoolService.mapLanguageToSpacyModel(grammarChecker.selectedLang)) | translate}} - </span> - <span *ngIf="(grammarChecker.selectedLang === 'auto')" - #langSelect> - auto - </span> - <mat-select class="select-list" - #select - [(ngModel)]="grammarChecker.selectedLang"> - <mat-option *ngFor="let lang of grammarChecker.languages" - [value]="lang"> - <span *ngIf="lang == 'de-DE'">{{ 'spacy-dialog.de' | translate }}</span> - <span *ngIf="lang == 'en-US'">{{ 'spacy-dialog.en' | translate }}</span> - <span *ngIf="lang == 'fr'">{{ 'spacy-dialog.fr' | translate }}</span> - <span *ngIf="lang == 'auto'">{{ 'spacy-dialog.auto' | translate }}</span> - </mat-option> - </mat-select> - </button> - </div> - <div class="anchor-wrp"> - <div class="anchor-right"> - <mat-form-field *ngIf="tags" - class="tag-form-field"> - <mat-label> - <mat-icon class="icon-svg" - svgIcon="comment_tag"></mat-icon> - {{'comment-page.tag' | translate}} - </mat-label> - <label for="tagSelect">{{selectedTag}}</label> - <mat-select [(ngModel)]="selectedTag" - class="tag-select" - id="tagSelect" - style="display: inline"> - <mat-option>{{'comment-page.tag-reset' | translate}}</mat-option> - <mat-option *ngFor="let tag of tags" - value="{{tag}}">{{tag}}</mat-option> - </mat-select> - </mat-form-field> - </div> - </div> - <mat-tab-group (selectedTabChange)="tempEditView = commentBody.innerText"> - <mat-tab label="{{ 'comment-page.write-comment' | translate }}"> - <ars-row [height]="12"></ars-row> - <ars-row> - - </ars-row> - <ars-row [height]="12"></ars-row> - <ars-row [overflow]="'visible'" - style="max-height:calc( 100vh - 250px )"> - <mat-form-field style="width:100%;"> - <input [disabled]="true" - matInput> - <div - (document:click)="grammarChecker.onDocumentClick($event)" - [contentEditable]="true" - (paste)="grammarChecker.onPaste($event); grammarChecker.maxLength(commentBody, user.role === 3 ? 1000 : 500)" - [spellcheck]="false" - (focus)="eventService.makeFocusOnInputTrue()" - style="margin-top:15px;width:100%;" - (blur)="eventService.makeFocusOnInputFalse()" - #commentBody - aria-labelledby="ask-question-description" - autofocus - (input)="grammarChecker.maxLength(commentBody, user.role === 3 ? 1000 : 500)" - id="answer-input"> - </div> - <mat-placeholder class="placeholder"> - {{ 'comment-page.enter-comment' | translate }} - </mat-placeholder> - <mat-hint align="start"> - <span aria-hidden="true"> - {{ 'comment-page.Markdown-hint' | translate }} - </span> - </mat-hint> - <mat-hint align="end"> - <span aria-hidden="true"> - {{commentBody.innerText.length}} / {{user.role === 3 ? 1000 : 500}} - </span> - </mat-hint> - <span *ngIf="!grammarChecker.hasSpellcheckConfidence"> - <p class="lang-confidence">{{ 'spacy-dialog.force-language-selection' | translate }}</p> - </span> - - </mat-form-field> - </ars-row> - </mat-tab> - <mat-tab label="{{ 'comment-page.preview-comment' | translate }}" - [disabled]="!commentBody.innerText"> - <ars-row [height]="12"></ars-row> - <ars-row> - - </ars-row> - <ars-row [height]="12"></ars-row> - <ars-row> - <markdown katex - emoji - lineNumbers - lineHighlight - [data]="tempEditView"></markdown> - </ars-row> - </mat-tab> - </mat-tab-group> - </ars-row> - <ars-row style="margin-top:8px"> - - </ars-row> - <ars-row ars-flex-box - class="spellcheck"> - <ars-col> - <button - [disabled]="this.commentBody.innerHTML.length < 4 " - mat-flat-button - class="spell-button" - (click)="grammarChecker.grammarCheck(commentBody)"> - {{ 'comment-page.grammar-check' | translate}} - <mat-icon *ngIf="grammarChecker.isSpellchecking" - class="spinner-container"> - <app-mat-spinner-overlay diameter="20" strokeWidth="2" [color]="'on-primary'"></app-mat-spinner-overlay> - </mat-icon> - </button> - </ars-col> - <ars-col> - <app-dialog-action-buttons - buttonsLabelSection="comment-page" - confirmButtonLabel="send" - [showLoadingCycle]="isSendingToSpacy" - [showDivider]="false" - [spacing]="false" - [cancelButtonClickAction]="buildCloseDialogActionCallback()" - [confirmButtonClickAction]="buildCreateCommentActionCallback(commentBody)" - ></app-dialog-action-buttons> - </ars-col> - </ars-row> + <app-write-comment [confirmLabel]="'send'" + [onSubmit]="buildCreateCommentActionCallback()" + [onClose]="buildCloseDialogActionCallback()" + [isSpinning]="isSendingToSpacy" + [tags]="tags" + [user]="user"> + </app-write-comment> </ars-row> 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 4882ec336d862bc588af06371f91514691af7347..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 @@ -1,246 +0,0 @@ -button { - min-width: 80px; -} - -form { - display: block; - width: 100%; - max-width: 800px; - margin-bottom: 50px; -} - -app-comment-list { - width: 100%; - max-width: 800px; -} - -#answer-input { - line-height: 120%; - color: var(--on-surface); - caret-color: var(--on-surface); - -webkit-appearance: textarea; - min-height: 50px; - cursor: text; - word-wrap: break-word; - - &:focus { - outline: none; - } -} - -.spell-button { - background-color: var(--primary); - color: var(--on-primary); - margin-top: 1rem; -} - -.send { - color: var(--on-primary); - background-color: var(--primary); -} - -::ng-deep .mat-select-value { - width: auto !important; -} - -.material-icons { - margin-right: 18px; -} - -.select-list { - width: calc(100% - 24px); -} - -.lang-selection { - vertical-align: middle; - margin-right: 0; -} - -mat-hint { - color: var(--on-surface) !important; -} - -.mat-select-value-text { - color: var(--on-surface); - caret-color: var(--on-surface); -} - -.placeholder { - color: var(--on-surface); -} - -.tag-form-field { - @media screen and (max-width: 500px) { - width: 70px; - } - z-index: 10000; -} - -.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: 100%; - height: 0; - position: relative; - left: 0; - top: 0; -} - -::ng-deep .mat-form-field-label { - color: var(--on-surface) !important; -} - -::ng-deep .mat-form-field-underline { - background-color: var(--on-surface) !important; -} - -::ng-deep .mat-form-field-ripple { - background-color: var(--on-surface) !important; -} - -::ng-deep .mat-select-arrow-wrapper .mat-select-arrow { - color: var(--on-surface); - margin-right: 50px; -} - -::ng-deep .mat-select-value-text { - color: var(--on-surface); -} - -::ng-deep .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) { - color: var(--primary); -} - -::ng-deep .mat-select-panel { - background: var(--dialog); -} - -.mat-option { - color: var(--on-surface); -} - -::ng-deep .mat-tab-body-content { - max-width: 540px !important; - overflow-x: hidden !important; -} - -.lang-confidence { - color: var(--on-cancel); - background-color: var(--cancel); - font-size: 16px; - padding: 5px; -} - -@keyframes shake { - 0% { - transform: translate(1px, 1px) rotate(0deg); - } - 10% { - transform: translate(0px, 0) rotate(-1deg); - } - 20% { - transform: translate(0px, 0px) rotate(1deg); - } - 30% { - transform: translate(3px, 2px) rotate(0deg); - } - 40% { - transform: translate(1px, 0) rotate(1deg); - } - 50% { - transform: translate(-1px, 2px) rotate(-1deg); - } - 60% { - transform: translate(-1px, 1px) rotate(0deg); - } - 70% { - transform: translate(3px, 1px) rotate(-1deg); - } - 80% { - transform: translate(-1px, 0) rotate(1deg); - } - 90% { - transform: translate(1px, 2px) rotate(0deg); - } - 100% { - transform: translate(1px, -0) rotate(-1deg); - } -} - -.spellcheck { - @media screen and (max-width: 500px) { - overflow: auto; - display: flex; - justify-content: space-between; - flex-direction: column !important; - flex-wrap: wrap; - align-items: flex-end; - } -} - -::ng-deep .mat-form-field .mat-form-field-infix { - max-width: 500px; -} - -.mat-flat-button.mat-primary.mat-button-disabled, .mat-flat-button.mat-accent.mat-button-disabled, .mat-flat-button.mat-warn.mat-button-disabled, .mat-flat-button.mat-button-disabled.mat-button-disabled, .mat-raised-button.mat-primary.mat-button-disabled, .mat-raised-button.mat-accent.mat-button-disabled, .mat-raised-button.mat-warn.mat-button-disabled, .mat-raised-button.mat-button-disabled.mat-button-disabled, .mat-fab.mat-primary.mat-button-disabled, .mat-fab.mat-accent.mat-button-disabled, .mat-fab.mat-warn.mat-button-disabled, .mat-fab.mat-button-disabled.mat-button-disabled, .mat-mini-fab.mat-primary.mat-button-disabled, .mat-mini-fab.mat-accent.mat-button-disabled, .mat-mini-fab.mat-warn.mat-button-disabled, .mat-mini-fab.mat-button-disabled.mat-button-disabled { - display: none; -} - -.spinner-container { - font-size: 14px; - margin-right: 7px; - margin-top: -2px; -} - -/* -Suggestion classes from Languagetool - */ - -::ng-deep .markUp { - position: relative; - display: inline-block; - border-bottom: 1px dotted black; - - > span { - text-decoration: underline wavy red; - cursor: pointer; - } - - > .dropdownBlock { - 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%; - - > .suggestions { - color: black; - display: block; - text-align: center; - cursor: pointer; - } - - > .error-message { - color: black; - display: block; - text-align: center; - } - } -} 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 e6b5257d8445fa962c914da3aa9a559857769fdf..654bb7aa9d5c097f383da2811c3a1d81c71db76c 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 { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { Comment, Language as CommentLanguage } from '../../../../models/comment'; import { NotificationService } from '../../../../services/util/notification.service'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -16,10 +16,7 @@ import { GrammarChecker } from '../../../../utils/grammar-checker'; templateUrl: './create-comment.component.html', styleUrls: ['./create-comment.component.scss'] }) -export class CreateCommentComponent implements OnInit, AfterViewInit { - - @ViewChild('langSelect') langSelect: ElementRef<HTMLDivElement>; - @ViewChild('commentBody') commentBody: ElementRef<HTMLDivElement>; +export class CreateCommentComponent implements OnInit { comment: Comment; user: User; @@ -35,7 +32,6 @@ export class CreateCommentComponent implements OnInit, AfterViewInit { public dialogRef: MatDialogRef<CommentListComponent>, private translateService: TranslateService, public dialog: MatDialog, - private translationService: TranslateService, public languagetoolService: LanguagetoolService, public eventService: EventService, @Inject(MAT_DIALOG_DATA) public data: any) { @@ -46,10 +42,6 @@ export class CreateCommentComponent implements OnInit, AfterViewInit { this.translateService.use(localStorage.getItem('currentLang')); } - ngAfterViewInit() { - this.grammarChecker.initBehavior(() => this.commentBody.nativeElement, () => this.langSelect.nativeElement); - } - onNoClick(): void { this.dialogRef.close(); } @@ -57,7 +49,7 @@ export class CreateCommentComponent implements OnInit, AfterViewInit { checkInputData(body: string): boolean { body = body.trim(); if (!body) { - this.translationService.get('comment-page.error-comment').subscribe(message => { + this.translateService.get('comment-page.error-comment').subscribe(message => { this.notification.show(message); }); return false; @@ -115,17 +107,11 @@ export class CreateCommentComponent implements OnInit, AfterViewInit { }); } - /** - * Returns a lambda which closes the dialog on call. - */ buildCloseDialogActionCallback(): () => void { return () => this.onNoClick(); } - /** - * Returns a lambda which executes the dialog dedicated action on call. - */ - buildCreateCommentActionCallback(text: HTMLDivElement): () => void { - return () => this.closeDialog(text.innerText); + buildCreateCommentActionCallback(): (string) => void { + return (text: string) => this.closeDialog(text); } } diff --git a/src/app/components/shared/comment-answer/comment-answer.component.html b/src/app/components/shared/comment-answer/comment-answer.component.html index 57c911f4287403b2c895640e6bb8d85afa9b6e5f..3f55e448d913b9a160454c159ff27044beff917a 100644 --- a/src/app/components/shared/comment-answer/comment-answer.component.html +++ b/src/app/components/shared/comment-answer/comment-answer.component.html @@ -10,110 +10,32 @@ fxLayoutAlign="center"> <mat-card class="answer border-answer" *ngIf="!isStudent || answer"> - <div *ngIf="(isStudent || !edit) && answer"> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="answer"></markdown> - <div fxLayout="row" - fxLayoutAlign="end"> - <button mat-raised-button - *ngIf="!isStudent && !edit" - class="save" - (click)="onEditClick()"> - <mat-icon>edit</mat-icon> - {{'comment-page.edit-answer' | translate}} - </button> - </div> - </div> - <div class="lang-selection" - *ngIf="!isStudent && (edit || !answer)"> - <button class="lang-btn" mat-button (click)="select.open()" - matTooltip="{{ 'spacy-dialog.lang-button-hint' | translate }}" - matTooltipShowDelay="750"> - <i class="material-icons">language</i> - <span *ngIf="!(grammarChecker.selectedLang === 'auto')"> - {{'spacy-dialog.' + (languagetoolService.mapLanguageToSpacyModel(grammarChecker.selectedLang)) | translate}} - </span> - <span *ngIf="(grammarChecker.selectedLang === 'auto')" #langSelect> - auto - </span> - <mat-select class="select-list" #select [(ngModel)]="grammarChecker.selectedLang"> - <mat-option *ngFor="let lang of grammarChecker.languages" [value]="lang"> - <span *ngIf="lang == 'de-DE'">{{ 'spacy-dialog.de' | translate }}</span> - <span *ngIf="lang == 'en-US'">{{ 'spacy-dialog.en' | translate }}</span> - <span *ngIf="lang == 'fr'">{{ 'spacy-dialog.fr' | translate }}</span> - <span *ngIf="lang == 'auto'">{{ 'spacy-dialog.auto' | translate }}</span> - </mat-option> - </mat-select> - </button> - </div> - <div *ngIf="!isStudent && (edit || !answer)"> - <mat-tab-group [dynamicHeight]="false" (selectedTabChange)="tempEditView = commentBody.innerText"> - <mat-tab label="{{'comment-page.your-answer' | translate}}"> - - <mat-form-field class="input-block"> - <input [disabled]="true" matInput> - <div - (document:click)="grammarChecker.onDocumentClick($event)" - [contentEditable]="true" - (paste)="grammarChecker.onPaste($event); grammarChecker.maxLength(commentBody, 2000)" - [spellcheck]="false" - (focus)="eventService.makeFocusOnInputTrue()" - style="margin-top:1.25em; width:100%;" - (blur)="eventService.makeFocusOnInputFalse()" - #commentBody - aria-labelledby="ask-question-description" - autofocus - (input)="grammarChecker.maxLength(commentBody, 2000);" - id="answer-input"> - </div> - <mat-hint align="start"> - <span aria-hidden="true"> - {{ 'comment-page.Markdown-hint' | translate }} - </span> - </mat-hint> - <mat-hint align="end"> - <span aria-hidden="true"> - {{ commentBody.innerText ? commentBody.innerText.length : 0 }} / 2000 - </span> - </mat-hint> - </mat-form-field> - </mat-tab> - <mat-tab label="{{'session.preview' | translate}}" - [disabled]="!commentBody.innerText"> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="tempEditView"></markdown> - </mat-tab> - </mat-tab-group> - <ars-row ars-flex-box class="spellcheck"> - <ars-col> - <button - *ngIf="edit" - [disabled]="commentBody.innerHTML.length < 4 " - mat-raised-button - (click)="grammarChecker.grammarCheck(commentBody)" - class="spell-button"> - {{ 'comment-page.grammar-check' | translate}} - <mat-icon *ngIf="grammarChecker.isSpellchecking" - class="spinner-container"> - <app-mat-spinner-overlay diameter="20" strokeWidth="2" [color]="'on-primary'"></app-mat-spinner-overlay> - </mat-icon> - </button> - </ars-col> - <ars-col style="display: flex; flex-direction: row;"> - <button mat-raised-button - *ngIf="answer || commentBody.innerText" - class="delete" - style="display: inline-block" - (click)="openDeleteAnswerDialog()"> - {{'comment-page.delete-answer' | translate}}</button> - <button mat-raised-button - class="save" - style="display: inline-block" - (click)="answer = commentBody.innerText; saveAnswer()"> - {{'comment-page.save-answer' | translate}}</button> - </ars-col> - </ars-row> - </div> + <app-write-comment [user]="user" + [onClose]="openDeleteAnswerDialog()" + [onSubmit]="saveAnswer()" + [disableCancelButton]="!answer && commentComponent && commentComponent.commentBody && !commentComponent.commentBody.nativeElement.innerText" + [confirmLabel]="'save-answer'" + [cancelLabel]="'delete-answer'" + [additionalTemplate]="editAnswer" + [enabled]="!isStudent && (edit || !answer)"> + </app-write-comment> </mat-card> </div> </div> + +<ng-template #editAnswer> + <div *ngIf="(isStudent || !edit) && answer"> + <markdown class="imborder-answerages" katex emoji lineNumbers lineHighlight + [data]="answer"></markdown> + <div fxLayout="row" + fxLayoutAlign="end"> + <button mat-raised-button + *ngIf="!isStudent && !edit" + class="save" + (click)="onEditClick()"> + <mat-icon>edit</mat-icon> + {{'comment-page.edit-answer' | translate}} + </button> + </div> + </div> +</ng-template> diff --git a/src/app/components/shared/comment-answer/comment-answer.component.scss b/src/app/components/shared/comment-answer/comment-answer.component.scss index 5af943e0ad1bc9980ca6f9c52b4cc6b10c1f5956..17448b00544cb65fb8212e99b507b783ed8596e4 100644 --- a/src/app/components/shared/comment-answer/comment-answer.component.scss +++ b/src/app/components/shared/comment-answer/comment-answer.component.scss @@ -29,12 +29,6 @@ button { margin-right: 1%; } -.delete { - background-color: var(--red); - color: var(--white); - margin-right: 20px; -} - mat-icon { font-size: 18px; height: 18px; @@ -45,121 +39,3 @@ mat-icon { .border-answer { box-shadow: 0 2px 1px -1px rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .14), 0 1px 3px 0 rgba(0, 0, 0, .12), -4px 0 0 0 var(--primary); } - -.material-icons { - margin-right: 18px; -} - -.select-list { - width: calc(100% - 24px); -} - -.lang-selection { - vertical-align: middle; - margin-right: 0; -} - -::ng-deep .mat-select-value { - width: auto !important; -} - -::ng-deep .mat-select-arrow-wrapper .mat-select-arrow { - color: var(--on-surface); - margin-right: 50px; -} - -::ng-deep .mat-select-panel { - background: var(--dialog); -} - -.mat-option { - color: var(--on-surface); -} - -.spell-button{ - background-color: var(--primary); - color: var(--on-primary); - display: inline-block -} - -#buttonWrapper { - justify-content: space-between; -} - -#answer-input { - line-height: 120%; - color: var(--on-surface); - caret-color: var(--on-surface); - -webkit-appearance: textarea; - min-height: 50px; - cursor: text; - word-wrap: break-word; - - &:focus { - outline: none; - } -} - -.spellcheck { - @media screen and (max-width:500px) { - overflow: auto; - display: flex; - justify-content: space-between; - flex-direction: column !important; - flex-wrap: wrap; - align-items: flex-end; - } -} - -.mat-flat-button.mat-primary.mat-button-disabled, .mat-flat-button.mat-accent.mat-button-disabled, .mat-flat-button.mat-warn.mat-button-disabled, .mat-flat-button.mat-button-disabled.mat-button-disabled, .mat-raised-button.mat-primary.mat-button-disabled, .mat-raised-button.mat-accent.mat-button-disabled, .mat-raised-button.mat-warn.mat-button-disabled, .mat-raised-button.mat-button-disabled.mat-button-disabled, .mat-fab.mat-primary.mat-button-disabled, .mat-fab.mat-accent.mat-button-disabled, .mat-fab.mat-warn.mat-button-disabled, .mat-fab.mat-button-disabled.mat-button-disabled, .mat-mini-fab.mat-primary.mat-button-disabled, .mat-mini-fab.mat-accent.mat-button-disabled, .mat-mini-fab.mat-warn.mat-button-disabled, .mat-mini-fab.mat-button-disabled.mat-button-disabled { - display: none; -} - -.spinner-container { - font-size: 14px; - margin-right: 7px; - margin-top: -6px; -} - -/* -Suggestion classes from Languagetool - */ - -::ng-deep .markUp { - position: relative; - display: inline-block; - border-bottom: 1px dotted black; - - > span { - text-decoration: underline wavy red; - cursor: pointer; - } - - > .dropdownBlock { - 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%; - - > .suggestions { - color: black; - display: block; - text-align: center; - cursor: pointer; - } - - > .error-message { - color: black; - display: block; - text-align: center; - } - } -} diff --git a/src/app/components/shared/comment-answer/comment-answer.component.ts b/src/app/components/shared/comment-answer/comment-answer.component.ts index 22539f734bc37be9c10a65d269002678f82195a0..d1a60c5a615a446c9e357e2e5e58b4ffc6623752 100644 --- a/src/app/components/shared/comment-answer/comment-answer.component.ts +++ b/src/app/components/shared/comment-answer/comment-answer.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; @@ -12,18 +12,17 @@ import { NotificationService } from '../../../services/util/notification.service import { MatDialog } from '@angular/material/dialog'; import { DeleteAnswerComponent } from '../../creator/_dialogs/delete-answer/delete-answer.component'; import { LanguagetoolService } from '../../../services/http/languagetool.service'; -import { GrammarChecker } from '../../../utils/grammar-checker'; import { EventService } from '../../../services/util/event.service'; +import { WriteCommentComponent } from '../write-comment/write-comment.component'; @Component({ selector: 'app-comment-answer', templateUrl: './comment-answer.component.html', styleUrls: ['./comment-answer.component.scss'] }) -export class CommentAnswerComponent implements OnInit, AfterViewInit { +export class CommentAnswerComponent implements OnInit { - @ViewChild('langSelect') langSelect: ElementRef<HTMLSpanElement>; - @ViewChild('commentBody') commentBody: ElementRef<HTMLDivElement>; + @ViewChild(WriteCommentComponent) commentComponent: WriteCommentComponent; comment: Comment; answer: string; @@ -32,9 +31,6 @@ export class CommentAnswerComponent implements OnInit, AfterViewInit { isStudent = true; edit = false; - grammarChecker: GrammarChecker; - tempEditView: string; - constructor(protected route: ActivatedRoute, private notificationService: NotificationService, private translateService: TranslateService, @@ -45,7 +41,6 @@ export class CommentAnswerComponent implements OnInit, AfterViewInit { public languagetoolService: LanguagetoolService, public dialog: MatDialog, public eventService: EventService) { - this.grammarChecker = new GrammarChecker(languagetoolService); } ngOnInit() { @@ -63,33 +58,34 @@ export class CommentAnswerComponent implements OnInit, AfterViewInit { }); } - ngAfterViewInit() { - this.grammarChecker.initBehavior(() => this.commentBody.nativeElement, () => this.langSelect.nativeElement); - } - - saveAnswer() { - this.edit = !this.answer; - this.commentService.answer(this.comment, this.answer).subscribe(); - this.translateService.get('comment-page.comment-answered').subscribe(msg => { - this.notificationService.show(msg); - }); + saveAnswer(): (string) => void { + return (text: string) => { + this.answer = text; + this.edit = !this.answer; + this.commentService.answer(this.comment, this.answer).subscribe(); + this.translateService.get('comment-page.comment-answered').subscribe(msg => { + this.notificationService.show(msg); + }); + }; } - openDeleteAnswerDialog(): void { - const dialogRef = this.dialog.open(DeleteAnswerComponent, { - width: '400px' - }); - dialogRef.afterClosed() - .subscribe(result => { - if (result === 'delete') { - this.deleteAnswer(); - } + openDeleteAnswerDialog(): () => void { + return () => { + const dialogRef = this.dialog.open(DeleteAnswerComponent, { + width: '400px' }); + dialogRef.afterClosed() + .subscribe(result => { + if (result === 'delete') { + this.deleteAnswer(); + } + }); + } } deleteAnswer() { - if (this.commentBody) { - this.commentBody.nativeElement.innerText = ''; + if (this.commentComponent.commentBody) { + this.commentComponent.commentBody.nativeElement.innerText = ''; } this.answer = null; this.commentService.answer(this.comment, this.answer).subscribe(); @@ -101,7 +97,7 @@ export class CommentAnswerComponent implements OnInit, AfterViewInit { onEditClick() { this.edit = true; setTimeout(() => { - this.commentBody.nativeElement.innerText = this.answer; + this.commentComponent.commentBody.nativeElement.innerText = this.answer; }); } } diff --git a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html index 1d8f72a6ab3c06dc65b2df6431990c84828187ed..1f6ed32cfd5f2b5a741d60484e4933e9628835ac 100644 --- a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html +++ b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.html @@ -30,7 +30,7 @@ class="cancel-button" attr.aria-labelledby="{{ ariaPrefix + 'cancel' | translate }}" (click)="performCancelButtonClickAction()" - >{{ buttonsLabelSection + '.cancel' | translate}}</button> + >{{ buttonsLabelSection + '.' + cancelButtonLabel | translate}}</button> </div> </div> diff --git a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts index 56c12bac7b2c8e2a2135e7bec99ef26213677ee7..9c52a26c86fd1a7c0cc143b9ce2a80e0a1f2779d 100644 --- a/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts +++ b/src/app/components/shared/dialog/dialog-action-buttons/dialog-action-buttons.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, Output } from '@angular/core'; +import { Component, OnInit, Input } from '@angular/core'; /** * Available confirm button types. @@ -44,6 +44,11 @@ export class DialogActionButtonsComponent implements OnInit { */ @Input() confirmButtonLabel: string; + /** + * The i18n label identifier of the confirm button. + */ + @Input() cancelButtonLabel = 'cancel'; + /** * The confirm button type. diff --git a/src/app/components/shared/shared.module.ts b/src/app/components/shared/shared.module.ts index 82b50527451336214fe2459ae6948a0b6d789042..1256415f0910cc9056e05b37c37c20863d062c14 100644 --- a/src/app/components/shared/shared.module.ts +++ b/src/app/components/shared/shared.module.ts @@ -46,6 +46,7 @@ import { TagCloudComponent } from './tag-cloud/tag-cloud.component'; import { JoyrideTemplateComponent } from './_dialogs/joyride-template/joyride-template.component'; import { JoyrideTemplateDirective } from '../../directives/joyride-template.directive'; import { MatSpinnerOverlayComponent } from './mat-spinner-overlay/mat-spinner-overlay.component'; +import { WriteCommentComponent } from './write-comment/write-comment.component'; @NgModule({ imports: [ @@ -97,7 +98,8 @@ import { MatSpinnerOverlayComponent } from './mat-spinner-overlay/mat-spinner-ov AutofocusDirective, JoyrideTemplateComponent, JoyrideTemplateDirective, - MatSpinnerOverlayComponent + MatSpinnerOverlayComponent, + WriteCommentComponent ], exports: [ RoomJoinComponent, diff --git a/src/app/components/shared/write-comment/write-comment.component.html b/src/app/components/shared/write-comment/write-comment.component.html new file mode 100644 index 0000000000000000000000000000000000000000..38596d969fe9a33f47086d3fffe971670aee7d9c --- /dev/null +++ b/src/app/components/shared/write-comment/write-comment.component.html @@ -0,0 +1,143 @@ +<ars-row> + <ng-container [ngTemplateOutlet]="additionalTemplate"></ng-container> + <div class="lang-selection" *ngIf="enabled"> + <button class="lang-btn" + mat-button + (click)="select.open()" + matTooltip="{{ 'spacy-dialog.lang-button-hint' | translate }}" + matTooltipShowDelay="750"> + <mat-icon id="langSymbol">language</mat-icon> + <span *ngIf="!(grammarChecker.selectedLang === 'auto')"> + {{'spacy-dialog.' + (languagetoolService.mapLanguageToSpacyModel(grammarChecker.selectedLang)) | translate}} + </span> + <span *ngIf="(grammarChecker.selectedLang === 'auto')" + #langSelect> + auto + </span> + <mat-select class="select-list" + #select + [(ngModel)]="grammarChecker.selectedLang"> + <mat-option *ngFor="let lang of grammarChecker.languages" + [value]="lang"> + <span *ngIf="lang == 'de-DE'">{{ 'spacy-dialog.de' | translate }}</span> + <span *ngIf="lang == 'en-US'">{{ 'spacy-dialog.en' | translate }}</span> + <span *ngIf="lang == 'fr'">{{ 'spacy-dialog.fr' | translate }}</span> + <span *ngIf="lang == 'auto'">{{ 'spacy-dialog.auto' | translate }}</span> + </mat-option> + </mat-select> + </button> + </div> + <div class="anchor-wrp" *ngIf="enabled"> + <div class="anchor-right"> + <mat-form-field *ngIf="tags" + class="tag-form-field"> + <mat-label> + <mat-icon class="icon-svg" + svgIcon="comment_tag"></mat-icon> + {{'comment-page.tag' | translate}} + </mat-label> + <label for="tagSelect">{{selectedTag}}</label> + <mat-select [(ngModel)]="selectedTag" + class="tag-select" + id="tagSelect"> + <mat-option>{{'comment-page.tag-reset' | translate}}</mat-option> + <mat-option *ngFor="let tag of tags" + value="{{tag}}">{{tag}}</mat-option> + </mat-select> + </mat-form-field> + </div> + </div> + <mat-tab-group (selectedTabChange)="tempEditView = commentBody.innerText" *ngIf="enabled"> + <mat-tab label="{{ 'comment-page.write-comment' | translate }}"> + <ars-row [height]="12"></ars-row> + <ars-row> + + </ars-row> + <ars-row [height]="12"></ars-row> + <ars-row [overflow]="'visible'" + class="comment-write-container"> + <mat-form-field class="full-width"> + <input [disabled]="true" + matInput> + <div + (document:click)="grammarChecker.onDocumentClick($event)" + [contentEditable]="true" + (paste)="grammarChecker.onPaste($event); grammarChecker.maxLength(commentBody, user.role === 3 ? 1000 : 500)" + [spellcheck]="false" + (focus)="eventService.makeFocusOnInputTrue()" + (blur)="eventService.makeFocusOnInputFalse()" + #commentBody + aria-labelledby="ask-question-description" + autofocus + (input)="grammarChecker.maxLength(commentBody, user.role === 3 ? 1000 : 500)" + id="answer-input"> + </div> + <mat-placeholder class="placeholder"> + {{ 'comment-page.enter-comment' | translate }} + </mat-placeholder> + <mat-hint align="start"> + <span aria-hidden="true"> + {{ 'comment-page.Markdown-hint' | translate }} + </span> + </mat-hint> + <mat-hint align="end"> + <span aria-hidden="true"> + {{commentBody.innerText.length}} / {{user.role === 3 ? 1000 : 500}} + </span> + </mat-hint> + <span *ngIf="!grammarChecker.hasSpellcheckConfidence"> + <p class="lang-confidence">{{ 'spacy-dialog.force-language-selection' | translate }}</p> + </span> + + </mat-form-field> + </ars-row> + </mat-tab> + <mat-tab label="{{ 'comment-page.preview-comment' | translate }}" + [disabled]="!commentBody.innerText"> + <ars-row [height]="12"></ars-row> + <ars-row> + + </ars-row> + <ars-row [height]="12"></ars-row> + <ars-row> + <markdown katex + emoji + lineNumbers + lineHighlight + [data]="tempEditView"></markdown> + </ars-row> + </mat-tab> + </mat-tab-group> +</ars-row> +<ars-row class="filler-row"> + +</ars-row> +<ars-row ars-flex-box + *ngIf="enabled" + class="spellcheck"> + <ars-col> + <button + [disabled]="this.commentBody && this.commentBody.nativeElement.innerHTML.length < 4 " + mat-flat-button + class="spell-button" + (click)="grammarChecker.grammarCheck(commentBody.nativeElement)"> + {{ 'comment-page.grammar-check' | translate}} + <mat-icon *ngIf="grammarChecker.isSpellchecking" + class="spinner-container"> + <app-mat-spinner-overlay diameter="20" strokeWidth="2" [color]="'on-primary'"></app-mat-spinner-overlay> + </mat-icon> + </button> + </ars-col> + <ars-col> + <app-dialog-action-buttons + buttonsLabelSection="comment-page" + [confirmButtonLabel]="confirmLabel" + [cancelButtonLabel]="cancelLabel" + [showLoadingCycle]="isSpinning" + [showDivider]="false" + [spacing]="false" + [cancelButtonClickAction]="buildCloseDialogActionCallback()" + [confirmButtonClickAction]="buildCreateCommentActionCallback()" + ></app-dialog-action-buttons> + </ars-col> +</ars-row> diff --git a/src/app/components/shared/write-comment/write-comment.component.scss b/src/app/components/shared/write-comment/write-comment.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..71dd4b064b72b7ac9a1dd534575c2f22c59d7de8 --- /dev/null +++ b/src/app/components/shared/write-comment/write-comment.component.scss @@ -0,0 +1,226 @@ +button { + min-width: 80px; +} + +.mat-flat-button.mat-primary.mat-button-disabled, .mat-flat-button.mat-accent.mat-button-disabled, +.mat-flat-button.mat-warn.mat-button-disabled, .mat-flat-button.mat-button-disabled.mat-button-disabled, +.mat-raised-button.mat-primary.mat-button-disabled, .mat-raised-button.mat-accent.mat-button-disabled, +.mat-raised-button.mat-warn.mat-button-disabled, .mat-raised-button.mat-button-disabled.mat-button-disabled, +.mat-fab.mat-primary.mat-button-disabled, .mat-fab.mat-accent.mat-button-disabled, +.mat-fab.mat-warn.mat-button-disabled, .mat-fab.mat-button-disabled.mat-button-disabled, +.mat-mini-fab.mat-primary.mat-button-disabled, .mat-mini-fab.mat-accent.mat-button-disabled, +.mat-mini-fab.mat-warn.mat-button-disabled, .mat-mini-fab.mat-button-disabled.mat-button-disabled { + display: none; +} + +.spell-button { + background-color: var(--primary); + color: var(--on-primary); + margin-top: 1rem; + animation: shake 1.5s; +} + +.spellcheck { + @media screen and (max-width: 500px) { + overflow: auto; + display: flex; + justify-content: space-between; + flex-direction: column !important; + flex-wrap: wrap; + align-items: flex-end; + } +} + +.spinner-container { + font-size: 14px; + margin-top: -2px; +} + +#answer-input { + line-height: 120%; + color: var(--on-surface); + caret-color: var(--on-surface); + -webkit-appearance: textarea; + min-height: 50px; + cursor: text; + word-wrap: break-word; + margin-top: 15px; + width: 100%; + + &:focus { + outline: none; + } +} + +::ng-deep { + .mat-form-field-label { + color: var(--on-surface) !important; + } + + .mat-form-field-underline, .mat-form-field-ripple { + background-color: var(--on-surface) !important; + } + + .mat-tab-body-content { + max-width: 540px !important; + overflow-x: hidden !important; + } +} + +mat-hint { + color: var(--on-surface) !important; +} + +.placeholder { + color: var(--on-surface); +} + +.lang-confidence { + color: var(--on-cancel); + background-color: var(--cancel); + font-size: 16px; + padding: 5px; +} + +::ng-deep .mat-form-field .mat-form-field-infix { + max-width: 500px; +} + +.full-width { + width: 100%; +} + +.comment-write-container { + max-height: calc(100vh - 250px); +} + +.filler-row { + margin-top: 8px; +} + +/* +Styling for tag selection + */ + +#tagSelect { + display: inline; +} + +.tag-form-field { + @media screen and (max-width: 500px) { + width: 70px; + } + z-index: 10000; +} + +.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: 100%; + height: 0; + position: relative; + left: 0; + top: 0; +} + +/* +Styling for language select + */ + +#langSymbol { + margin-right: 18px; +} + +.select-list { + width: calc(100% - 24px); +} + +.lang-selection { + vertical-align: middle; + margin-right: 0; +} + +/* +Styling for tag selection and language selection + */ + +::ng-deep { + .mat-select-arrow-wrapper .mat-select-arrow { + color: var(--on-surface); + margin-right: 50px; + } + + .mat-select-value { + width: auto !important; + } + + .mat-select-value-text { + color: var(--on-surface); + caret-color: var(--on-surface); + } + + .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) { + color: var(--primary); + } + + .mat-select-panel { + background: var(--dialog); + } +} + +.mat-option { + color: var(--on-surface); +} + +/* +Suggestion classes from Languagetool + */ + +::ng-deep .markUp { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; + + > span { + text-decoration: underline wavy red; + cursor: pointer; + } + + > .dropdownBlock { + 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%; + + > .suggestions { + color: black; + display: block; + text-align: center; + cursor: pointer; + } + + > .error-message { + color: black; + display: block; + text-align: center; + } + } +} diff --git a/src/app/components/shared/write-comment/write-comment.component.spec.ts b/src/app/components/shared/write-comment/write-comment.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3207abc4ccdc0340b98b7eb0f6a907fefd947dc6 --- /dev/null +++ b/src/app/components/shared/write-comment/write-comment.component.spec.ts @@ -0,0 +1,26 @@ +/*import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteCommentComponent } from './write-comment.component'; + +describe('WriteCommentComponent', () => { + let component: WriteCommentComponent; + let fixture: ComponentFixture<WriteCommentComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ WriteCommentComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteCommentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); +*/ diff --git a/src/app/components/shared/write-comment/write-comment.component.ts b/src/app/components/shared/write-comment/write-comment.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ccdfb87a2ea07117ea0a693d8a6ba163a939cdee --- /dev/null +++ b/src/app/components/shared/write-comment/write-comment.component.ts @@ -0,0 +1,79 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { GrammarChecker } from '../../../utils/grammar-checker'; +import { TranslateService } from '@ngx-translate/core'; +import { LanguagetoolService } from '../../../services/http/languagetool.service'; +import { Comment } from '../../../models/comment'; +import { User } from '../../../models/user'; +import { NotificationService } from '../../../services/util/notification.service'; +import { EventService } from '../../../services/util/event.service'; + +@Component({ + selector: 'app-write-comment', + templateUrl: './write-comment.component.html', + styleUrls: ['./write-comment.component.scss'] +}) +export class WriteCommentComponent implements OnInit, AfterViewInit { + + @ViewChild('langSelect') langSelect: ElementRef<HTMLDivElement>; + @ViewChild('commentBody') commentBody: ElementRef<HTMLDivElement>; + @Input() user: User; + @Input() tags: string[]; + @Input() onClose: () => any; + @Input() onSubmit: (commentText: string, selectedTag: string) => any; + @Input() isSpinning = false; + @Input() disableCancelButton = false; + @Input() confirmLabel = 'save'; + @Input() cancelLabel = 'cancel'; + @Input() additionalTemplate: TemplateRef<any>; + @Input() enabled = true; + comment: Comment; + selectedTag: string; + grammarChecker: GrammarChecker; + tempEditView: string; + + + constructor(private notification: NotificationService, + private translateService: TranslateService, + public eventService: EventService, + public languagetoolService: LanguagetoolService) { + this.grammarChecker = new GrammarChecker(this.languagetoolService); + } + + ngOnInit(): void { + this.translateService.use(localStorage.getItem('currentLang')); + } + + ngAfterViewInit() { + this.grammarChecker.initBehavior(() => this.commentBody.nativeElement, () => this.langSelect.nativeElement); + } + + buildCloseDialogActionCallback(): () => void { + if (!this.onClose || this.disableCancelButton) { + return undefined; + } + return () => this.onClose(); + } + + buildCreateCommentActionCallback(): () => void { + if (!this.onSubmit) { + return undefined; + } + return () => { + if (this.checkInputData(this.commentBody.nativeElement.innerText)) { + this.onSubmit(this.commentBody.nativeElement.innerText, this.selectedTag); + } + }; + } + + private checkInputData(body: string): boolean { + body = body.trim(); + if (!body) { + this.translateService.get('comment-page.error-comment').subscribe(message => { + this.notification.show(message); + }); + return false; + } + return true; + } + +} diff --git a/src/app/services/http/languagetool.service.ts b/src/app/services/http/languagetool.service.ts index 8926ec14c52adb99fc7dea85ee57d1e390d1d48a..b2258ccebdc80bd3ed2f583bea458b3b68394be4 100644 --- a/src/app/services/http/languagetool.service.ts +++ b/src/app/services/http/languagetool.service.ts @@ -124,7 +124,7 @@ export class LanguagetoolService extends BaseHttpService { } }).pipe( tap(_ => ''), - timeout(500), + timeout(5000), catchError(this.handleError<any>('checkSpellings')) ); } diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 0a1067aaa48fab2313d48f3cde53c04f4466f2c0..0e9df81f96286c31f132a201a9639a61bb340cbd 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -118,6 +118,8 @@ "fullscreen": "Die Frage wurde beantwortet oder kommentiert. Klick auf das Symbol für die Ansicht.", "abort": "Abbrechen", "ask-question-description": "Gib hier deine Frage ein!", + "save-answer": "Speichern", + "delete-answer": "Löschen", "cancel": "Abbrechen", "cancel-description": "Abbrechen", "comment": "Die Frage {{ comment }} wurde um {{ time }} Uhr gestellt und hat derzeitig {{ votes }}. {{correct}} {{wrong}} {{bonus}} {{beamer}}", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 71ec990a036b6c9d0676d1d463483fa58106a2ae..2938841f0c97232ae79179b56b55630284a1b67b 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -128,6 +128,8 @@ "fullscreen": "Click for full view.", "abort": "Cancel", "ask-question-description": "Enter your question …", + "save-answer": "Save", + "delete-answer": "Delete", "cancel": "Cancel", "cancel-description": "Cancel", "comment": "Question {{ comment }} was asked at {{ time }} and has currently {{ votes }}. {{correct}} {{wrong}} {{bonus}} {{beamer}}",