From 96130fc5a3eed6e52277f826d72741589ebe23cd Mon Sep 17 00:00:00 2001
From: Alicia <Alicia.DeDiosFuente@unige.ch>
Date: Tue, 17 May 2022 14:55:27 +0200
Subject: [PATCH 1/2] feat: [AoU-1183] add limit size for rich text area

---
 .../rich-text-editor.presentational.html      |  6 ++++
 .../rich-text-editor.presentational.ts        | 34 ++++++++++++++++++-
 .../src/lib/models/label-translate.model.ts   |  1 +
 3 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html
index 6490e0700..30ed94e52 100644
--- a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html
+++ b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html
@@ -16,6 +16,7 @@
               [class.is-disabled]="formControl.disabled"
               (onFocus)="isFocused = true"
               (onBlur)="isFocused = false"
+              (onContentChanged)="preventLengthToExceed($event)"
               class="editor"
 ></quill-editor>
 <mat-error *ngIf="formValidationHelper.getFormError(formControl) as errors"
@@ -24,3 +25,8 @@
 <mat-hint *ngIf="withWordCounter | isTrue"
           class="word-counter"
 >{{numberWords}} {{labelTranslate.words | translate}}</mat-hint>
+
+<mat-hint *ngIf="withWordCounterAndMaxLength | isTrue"
+          class="word-counter"
+>( {{this.numberCharactersWritten}} {{labelTranslate.characters | translate}} / {{this.maxLength}}
+  {{labelTranslate.characters | translate}} {{labelTranslate.maximum | translate}} )</mat-hint>
diff --git a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts
index 664e01068..db4d29261 100644
--- a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts
+++ b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts
@@ -46,6 +46,7 @@ import {
   isNullOrUndefinedOrWhiteString,
 } from "../../../tools/is/is.tool";
 import {AbstractPresentational} from "../abstract/abstract.presentational";
+import {ContentChange} from "ngx-quill";
 
 @Component({
   selector: "solidify-rich-text-editor",
@@ -67,8 +68,17 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
   @Input()
   placeholder: string = "";
 
+  private _formControl: FormControl;
+
   @Input()
-  formControl: FormControl;
+  set formControl(formControl: FormControl) {
+    this._formControl = formControl;
+    this.numberCharactersWritten = formControl.value.length;
+  };
+
+  get formControl(): FormControl {
+    return this._formControl;
+  }
 
   @Input()
   modules: QuillModules = {
@@ -86,6 +96,19 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
 
   @ViewChild("quillEditorComponent")
   quillEditorComponent: QuillEditorComponent;
+
+  @Input()
+  maxLength: number | undefined = undefined;
+
+  numberCharactersWritten: number | undefined;
+
+  get withWordCounterAndMaxLength(): boolean {
+    if (this.maxLength !== undefined) {
+      return true;
+    }
+    return false;
+  }
+
   isFocused: boolean = false;
 
   get numberWords(): number {
@@ -104,6 +127,15 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
     return FormValidationHelper;
   }
 
+  preventLengthToExceed($event: ContentChange): void {
+    if (this.maxLength !== undefined) {
+      if ($event.editor.getLength() >= this.maxLength) {
+        $event.editor.deleteText(this.maxLength - 1, $event.editor.getLength());
+      }
+      this.numberCharactersWritten = $event.editor.getLength();
+    }
+  }
+
   constructor(@Inject(LABEL_TRANSLATE) readonly labelTranslate: LabelTranslateInterface,
               private readonly _changeDetector: ChangeDetectorRef) {
     super();
diff --git a/projects/solidify-frontend/src/lib/models/label-translate.model.ts b/projects/solidify-frontend/src/lib/models/label-translate.model.ts
index 26782c579..335f9be34 100644
--- a/projects/solidify-frontend/src/lib/models/label-translate.model.ts
+++ b/projects/solidify-frontend/src/lib/models/label-translate.model.ts
@@ -192,4 +192,5 @@ export interface LabelTranslateInterface {
   noCitationsAreAvailable: string;
   themeSelector: string;
   globalBannerClickForMoreInfo: string;
+  maximum: string;
 }
-- 
GitLab


From 02e704c1bbfe32915e6e68c99fa22a3fbbceeddc Mon Sep 17 00:00:00 2001
From: Florent Poittevin <florent.poittevin@unige.ch>
Date: Mon, 23 May 2022 15:24:06 +0200
Subject: [PATCH 2/2] fix(rich text): prevent user to exceed limit of char and
 allow to display raw input

---
 .../rich-text-editor.presentational.html      | 86 +++++++++++++------
 .../rich-text-editor.presentational.scss      |  4 +
 .../rich-text-editor.presentational.ts        | 53 ++++++------
 .../src/lib/models/label-translate.model.ts   |  2 +
 4 files changed, 93 insertions(+), 52 deletions(-)

diff --git a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html
index 30ed94e52..7abf1d977 100644
--- a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html
+++ b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.html
@@ -1,32 +1,70 @@
-<mat-label class="label"
-           [class.is-required]="formValidationHelper.hasRequiredField(formControl)"
-><span class="text">{{label | translate}}</span>
-  <span *ngIf="formValidationHelper.hasRequiredField(formControl)"
-        aria-hidden="true"
-        class="mat-placeholder-required mat-form-field-required-marker"
-  > *</span>
-</mat-label>
+<ng-template [ngIf]="isInRichTextMode"
+             [ngIfElse]="rawTextMode"
+>
+  <mat-label class="label"
+             [class.is-required]="formValidationHelper.hasRequiredField(formControl)"
+  ><span class="text">{{label | translate}}</span>
+    <span *ngIf="formValidationHelper.hasRequiredField(formControl)"
+          aria-hidden="true"
+          class="mat-placeholder-required mat-form-field-required-marker"
+    > *</span>
+  </mat-label>
+  <quill-editor #quillEditorComponent
+                [modules]="modules"
+                [formControl]="formControl"
+                [placeholder]="placeholder | translate"
+                [class.is-invalid]="formValidationHelper.displayInvalidWhenRequired(formControl, displayEmptyRequiredFieldInError)"
+                [class.is-focused]="isFocused"
+                [class.is-disabled]="formControl.disabled"
+                (onFocus)="isFocused = true"
+                (onBlur)="isFocused = false"
+                (onContentChanged)="preventLengthToExceed($event)"
+                class="editor"
+  ></quill-editor>
+</ng-template>
+
+<ng-template #rawTextMode>
+  <mat-form-field [appearance]="appearanceInputMaterial"
+                  [floatLabel]="positionLabelInputMaterial"
+                  [class.mat-form-field-invalid]="(formValidationHelper.hasRequiredField(formControl) && (formControl.value | isNullOrUndefinedOrWhiteString)) || formControl.invalid"
+                  class="mat-form-field"
+                  solidifyTooltipOnEllipsis
+  >
+    <mat-label>{{label | translate}}</mat-label>
+    <textarea [formControl]="formControl"
+              [required]="formValidationHelper.hasRequiredField(formControl)"
+              [maxLength]="maxLength !== 0 ? maxLength : null"
+              cdkAutosizeMaxRows="10"
+              cdkAutosizeMinRows="5"
+              cdkTextareaAutosize
+              matInput
+    >
+      </textarea>
+  </mat-form-field>
+</ng-template>
 
-<quill-editor #quillEditorComponent
-              [modules]="modules"
-              [formControl]="formControl"
-              [placeholder]="placeholder | translate"
-              [class.is-invalid]="formValidationHelper.displayInvalidWhenRequired(formControl, displayEmptyRequiredFieldInError)"
-              [class.is-focused]="isFocused"
-              [class.is-disabled]="formControl.disabled"
-              (onFocus)="isFocused = true"
-              (onBlur)="isFocused = false"
-              (onContentChanged)="preventLengthToExceed($event)"
-              class="editor"
-></quill-editor>
 <mat-error *ngIf="formValidationHelper.getFormError(formControl) as errors"
            class="errors"
 >{{errors}}</mat-error>
+
 <mat-hint *ngIf="withWordCounter | isTrue"
           class="word-counter"
->{{numberWords}} {{labelTranslate.words | translate}}</mat-hint>
+>{{numberWords}} {{labelTranslate.words | translate | lowercase}}</mat-hint>
 
-<mat-hint *ngIf="withWordCounterAndMaxLength | isTrue"
+<mat-hint *ngIf="withMaxLength | isTrue"
           class="word-counter"
->( {{this.numberCharactersWritten}} {{labelTranslate.characters | translate}} / {{this.maxLength}}
-  {{labelTranslate.characters | translate}} {{labelTranslate.maximum | translate}} )</mat-hint>
+>(
+  <ng-template [ngIf]="isInRichTextMode | isFalse"> {{formControl.value.length}} {{labelTranslate.characters |
+      translate | lowercase}} /
+  </ng-template>
+  {{maxLength}} {{labelTranslate.characters | translate | lowercase}} {{labelTranslate.maximum | translate | lowercase}} )
+</mat-hint>
+
+<ng-template [ngIf]="formControl.disabled | isFalse">
+  <a *ngIf="isInRichTextMode; else linkToggleToRichTextMode"
+     (click)="toggleTextMode()"
+  >{{labelTranslate.displayWithoutFormatting | translate}}</a>
+  <ng-template #linkToggleToRichTextMode>
+    <a (click)="toggleTextMode()">{{labelTranslate.displayWithFormatting | translate}}</a>
+  </ng-template>
+</ng-template>
diff --git a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.scss b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.scss
index e8f29d320..f144c50ca 100644
--- a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.scss
+++ b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.scss
@@ -122,6 +122,10 @@
       }
     }
   }
+
+  .mat-form-field {
+    display: grid;
+  }
 }
 
 @include isInDarkMode {
diff --git a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts
index db4d29261..0a8a9a1c9 100644
--- a/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts
+++ b/projects/solidify-frontend/src/lib/components/presentationals/rich-text-editor/rich-text-editor.presentational.ts
@@ -35,7 +35,10 @@ import {
   FormControl,
   NG_VALUE_ACCESSOR,
 } from "@angular/forms";
-import {QuillEditorComponent} from "ngx-quill";
+import {
+  ContentChange,
+  QuillEditorComponent,
+} from "ngx-quill";
 import {QuillModules} from "ngx-quill/lib/quill-editor.interfaces";
 import {tap} from "rxjs/operators";
 import {FormValidationHelper} from "../../../helpers/form-validation.helper";
@@ -44,9 +47,9 @@ import {LabelTranslateInterface} from "../../../models/label-translate.model";
 import {
   isNonEmptyString,
   isNullOrUndefinedOrWhiteString,
+  isNumberReal,
 } from "../../../tools/is/is.tool";
 import {AbstractPresentational} from "../abstract/abstract.presentational";
-import {ContentChange} from "ngx-quill";
 
 @Component({
   selector: "solidify-rich-text-editor",
@@ -68,17 +71,8 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
   @Input()
   placeholder: string = "";
 
-  private _formControl: FormControl;
-
   @Input()
-  set formControl(formControl: FormControl) {
-    this._formControl = formControl;
-    this.numberCharactersWritten = formControl.value.length;
-  };
-
-  get formControl(): FormControl {
-    return this._formControl;
-  }
+  formControl: FormControl;
 
   @Input()
   modules: QuillModules = {
@@ -100,17 +94,13 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
   @Input()
   maxLength: number | undefined = undefined;
 
-  numberCharactersWritten: number | undefined;
+  isFocused: boolean = false;
+  isInRichTextMode: boolean = true;
 
-  get withWordCounterAndMaxLength(): boolean {
-    if (this.maxLength !== undefined) {
-      return true;
-    }
-    return false;
+  get withMaxLength(): boolean {
+    return isNumberReal(this.maxLength);
   }
 
-  isFocused: boolean = false;
-
   get numberWords(): number {
     const text = this.formControl.value as string;
     if (isNullOrUndefinedOrWhiteString(text)) {
@@ -119,20 +109,18 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
     return text.split(" ").filter(term => isNonEmptyString(term)).length;
   }
 
-  get activeElement(): Element {
-    return document.activeElement;
-  }
-
   get formValidationHelper(): typeof FormValidationHelper {
     return FormValidationHelper;
   }
 
   preventLengthToExceed($event: ContentChange): void {
-    if (this.maxLength !== undefined) {
-      if ($event.editor.getLength() >= this.maxLength) {
-        $event.editor.deleteText(this.maxLength - 1, $event.editor.getLength());
+    if (this.withMaxLength) {
+      if ($event.html?.length > this.maxLength) {
+        const exceededCharLength = $event.html?.length - this.maxLength;
+        const textLength = $event.text?.length;
+        $event.editor.deleteText(textLength - exceededCharLength - 1, exceededCharLength);
+        this.formControl.setValue($event.editor.root.innerHTML);
       }
-      this.numberCharactersWritten = $event.editor.getLength();
     }
   }
 
@@ -151,6 +139,11 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
         this._changeDetector.detectChanges();
       }),
     ));
+    this.subscribe(this.formControl.valueChanges.pipe(
+      tap(() => {
+        this._changeDetector.detectChanges();
+      }),
+    ));
   }
 
   registerOnChange(fn: any): void {
@@ -164,4 +157,8 @@ export class RichTextEditorPresentational extends AbstractPresentational impleme
 
   setDisabledState(isDisabled: boolean): void {
   }
+
+  toggleTextMode(): void {
+    this.isInRichTextMode = !this.isInRichTextMode;
+  }
 }
diff --git a/projects/solidify-frontend/src/lib/models/label-translate.model.ts b/projects/solidify-frontend/src/lib/models/label-translate.model.ts
index 335f9be34..fa598b94e 100644
--- a/projects/solidify-frontend/src/lib/models/label-translate.model.ts
+++ b/projects/solidify-frontend/src/lib/models/label-translate.model.ts
@@ -193,4 +193,6 @@ export interface LabelTranslateInterface {
   themeSelector: string;
   globalBannerClickForMoreInfo: string;
   maximum: string;
+  displayWithFormatting: string;
+  displayWithoutFormatting: string;
 }
-- 
GitLab