Forráskód Böngészése

Add maximized mode to markdown-textarea + CSS improvements (#2660)

* Add arrows-angle-contract/expand bootstrap icons

* Add grey textarea-background-color

* Add maximized support to markdown-textarea + improve column display

* Refactor CSS + add ResizeObservable

* Replace bootstrap icons with softies

* Add ResizeObserver typing definition

* Add focus on textarea + Fix Observables

* Propage component changes on markdown plugins

* Ignore ResizeObserver not implemented in typescript yet

* Move observers from constructor to click event

* Add scss and css variables

* Replace textareaWidth with textareaMaxWidth to fix others textareas

* Clean unused css rules

* Fix ResizeObserver unknown by TypeScript compiler

* Set max-width: 100% for small and mobile views

* Fix textarea/preview height on maximized mode

* Add common padding textarea/preview side-by-side

* Hide scrollbar sub-menu on small-views

* Add maximized mode for mobile views

* Fix sass calculate syntax

* Revert custom CSS variable for inputBorderRadius and inputBorderColor

* Remove unsued methods

* Fix missing implement method

Co-authored-by: kimsible <kimsible@users.noreply.github.com>
Kim 4 éve
szülő
commit
b15fe00f74

+ 6 - 6
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html

@@ -37,7 +37,7 @@
               <div class="form-group">
                 <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help>
                 <my-markdown-textarea
-                  name="instanceDescription" formControlName="description" textareaWidth="500px" [previewColumn]="true"
+                  name="instanceDescription" formControlName="description" textareaMaxWidth="500px"
                   [classes]="{ 'input-error': formErrors['instance.description'] }"
                 ></my-markdown-textarea>
                 <div *ngIf="formErrors.instance.description" class="form-error">{{ formErrors.instance.description }}</div>
@@ -120,7 +120,7 @@
               <div class="form-group">
                 <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
                 <my-markdown-textarea
-                  name="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true"
+                  name="instanceTerms" formControlName="terms" textareaMaxWidth="500px"
                   [ngClass]="{ 'input-error': formErrors['instance.terms'] }"
                 ></my-markdown-textarea>
                 <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div>
@@ -129,7 +129,7 @@
               <div class="form-group">
                 <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help>
                 <my-markdown-textarea
-                  name="instanceCodeOfConduct" formControlName="codeOfConduct" textareaWidth="500px" [previewColumn]="true"
+                  name="instanceCodeOfConduct" formControlName="codeOfConduct" textareaMaxWidth="500px"
                   [ngClass]="{ 'input-error': formErrors['instance.codeOfConduct'] }"
                 ></my-markdown-textarea>
                 <div *ngIf="formErrors.instance.codeOfConduct" class="form-error">{{ formErrors.instance.codeOfConduct }}</div>
@@ -140,7 +140,7 @@
                 <div i18n class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div>
 
                 <my-markdown-textarea
-                  name="instanceModerationInformation" formControlName="moderationInformation" textareaWidth="500px" [previewColumn]="true"
+                  name="instanceModerationInformation" formControlName="moderationInformation" textareaMaxWidth="500px"
                   [ngClass]="{ 'input-error': formErrors['instance.moderationInformation'] }"
                 ></my-markdown-textarea>
                 <div *ngIf="formErrors.instance.moderationInformation" class="form-error">{{ formErrors.instance.moderationInformation }}</div>
@@ -161,7 +161,7 @@
                 <div i18n class="label-small-info">A single person? A non-profit? A company?</div>
 
                 <my-markdown-textarea
-                  name="instanceAdministrator" formControlName="administrator" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true"
+                  name="instanceAdministrator" formControlName="administrator" textareaMaxWidth="500px" textareaHeight="75px"
                   [classes]="{ 'input-error': formErrors['instance.administrator'] }"
                 ></my-markdown-textarea>
 
@@ -216,7 +216,7 @@
                 <div i18n class="label-small-info">i.e. 2vCore 2GB RAM, a direct the link to the server you rent, etc.</div>
 
                 <my-markdown-textarea
-                  name="instanceHardwareInformation" formControlName="hardwareInformation" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true"
+                  name="instanceHardwareInformation" formControlName="hardwareInformation" textareaMaxWidth="500px" textareaHeight="75px"
                   [classes]="{ 'input-error': formErrors['instance.hardwareInformation'] }"
                 ></my-markdown-textarea>
 

+ 0 - 12
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss

@@ -68,18 +68,6 @@ textarea {
   pointer-events: none;
 }
 
-my-markdown-textarea ::ng-deep {
-  .root {
-    @media screen and (max-width: 1400px) {
-      flex-direction: column !important;
-    }
-
-    textarea {
-      max-width: 100%;
-    }
-  }
-}
-
 .form-group-right {
   padding-top: 2px;
 }

+ 2 - 2
client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.html

@@ -19,13 +19,13 @@
 
       <my-markdown-textarea
         *ngIf="setting.type === 'markdown-text'"
-        markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" [previewColumn]="false"
+        markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px"
         [classes]="{ 'input-error': formErrors['settings.name'] }"
       ></my-markdown-textarea>
 
       <my-markdown-textarea
         *ngIf="setting.type === 'markdown-enhanced'"
-        markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" [previewColumn]="false"
+        markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px"
         [classes]="{ 'input-error': formErrors['settings.name'] }"
       ></my-markdown-textarea>
 

+ 4 - 4
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html

@@ -59,7 +59,7 @@
           {{ formErrors['display-name'] }}
         </div>
       </div>
-    
+
       <div class="form-group">
         <label i18n for="description">Description</label>
         <textarea
@@ -70,7 +70,7 @@
           {{ formErrors.description }}
         </div>
       </div>
-    
+
       <div class="form-group">
         <label for="support">Support</label>
         <my-help
@@ -78,14 +78,14 @@
     When you will upload a video in this channel, the video support field will be automatically filled by this text."
         ></my-help>
         <my-markdown-textarea
-            id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
+            id="support" formControlName="support" textareaMaxWidth="500px" markdownType="enhanced"
             [classes]="{ 'input-error': formErrors['support'] }"
         ></my-markdown-textarea>
         <div *ngIf="formErrors.support" class="form-error">
           {{ formErrors.support }}
         </div>
       </div>
-    
+
       <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
         <my-peertube-checkbox
           inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate"

+ 0 - 8
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss

@@ -48,14 +48,6 @@ textarea {
   display: block;
 }
 
-my-markdown-textarea ::ng-deep {
-  .root {
-    @media screen and (max-width: 1400px) {
-      flex-direction: column !important;
-    }
-  }
-}
-
 .peertube-select-container {
   @include peertube-select-container(340px);
 }

+ 12 - 4
client/src/app/shared/forms/markdown-textarea.component.html

@@ -1,12 +1,12 @@
-<div class="root" [ngStyle]="{ 'flex-direction': flexDirection }">
-  <textarea
+<div class="root" [ngClass]="{ 'maximized': isMaximized }" [ngStyle]="{ 'max-width': textareaMaxWidth }">
+  <textarea #textarea
     [(ngModel)]="content" (ngModelChange)="onModelChange()"
     class="form-control" [ngClass]="classes"
-    [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }"
+    [ngStyle]="{ height: textareaHeight }"
     [id]="name" [name]="name">
   </textarea>
 
-  <div ngbNav #nav="ngbNav" class="nav-pills previews">
+  <div ngbNav #nav="ngbNav" class="nav-pills nav-preview">
     <ng-container ngbNavItem *ngIf="truncate !== undefined">
       <a ngbNavLink i18n>Truncated preview</a>
 
@@ -22,6 +22,14 @@
         <div [innerHTML]="previewHTML"></div>
       </ng-template>
     </ng-container>
+
+    <my-button
+      *ngIf="!isMaximized" icon="fullscreen" (click)="onMaximizeClick()"
+    ></my-button>
+
+    <my-button
+      *ngIf="isMaximized" icon="exit-fullscreen" (click)="onMaximizeClick()"
+    ></my-button>
   </div>
 
   <div [ngbNavOutlet]="nav"></div>

+ 229 - 15
client/src/app/shared/forms/markdown-textarea.component.scss

@@ -1,36 +1,250 @@
 @import '_variables';
 @import '_mixins';
 
-.root {
-  display: flex;
+$nav-preview-tab-height: 30px;
+$base-padding: 15px;
+$input-border-color: #C6C6C6;
+$input-border-radius: 3px;
+
+@mixin in-small-view {
+  .root {
+    display: flex;
+    flex-direction: column;
+
+    textarea {
+      @include peertube-textarea(100%, 150px);
+
+      background-color: var(--textareaBackgroundColor);
+      font-family: courier, monospace;
+      font-size: 13px;
+      border-bottom: none;
+      border-bottom-left-radius: unset;
+      border-bottom-right-radius: unset;
+    }
 
-  textarea {
-    @include peertube-textarea(100%, 150px);
+    .nav-preview {
+      display: block;
+      text-align: right;
+      padding-top: 10px;
+      padding-bottom: 10px;
+      padding-left: 10px;
+      padding-right: 10px;
+      border-top: 1px dashed $input-border-color;
+      border-left: 1px solid $input-border-color;
+      border-right: 1px solid $input-border-color;
+      border-bottom: 1px solid $input-border-color;
+      border-bottom-right-radius: $input-border-radius;
 
-    margin-bottom: 15px;
+      border-bottom-left-radius: $input-border-radius;
+      ::ng-deep {
+        .nav-link {
+          display: none !important;
+        }
+
+        .grey-button {
+          padding: 0 12px 0 12px;
+        }
+      }
+    }
+
+    ::ng-deep {
+      .tab-content {
+        display: none;
+      }
+    }
   }
+}
+
+@mixin nav-preview-medium {
+  display: flex;
+  flex-grow: 1;
+  border-bottom-left-radius: unset;
+  border-bottom-right-radius: unset;
+  border-bottom: 2px solid var(--mainColor);
 
-  .previews {
-    max-height: 150px;
-    overflow-y: auto;
-    flex-grow: 1;
+  :first-child {
+    margin-left: auto;
   }
 
   ::ng-deep {
     .nav-link {
       display: flex !important;
       align-items: center;
-      height: 30px !important;
+      height: $nav-preview-tab-height !important;
       padding: 0 15px !important;
       font-size: 85% !important;
       opacity: .7;
     }
 
-    .tab-content {
-      min-height: 75px;
-      padding: 15px;
-      font-size: 15px;
-      word-wrap: break-word;
+    .grey-button {
+      margin-left: 5px;
+    }
+  }
+}
+
+@mixin content-preview-base {
+  display: block;
+  min-height: 75px;
+  padding: $base-padding;
+  overflow-y: auto;
+  font-size: 15px;
+  word-wrap: break-word;
+}
+
+@mixin maximized-base {
+  flex-direction: row;
+  z-index: #{z(header) - 1};
+  position: fixed;
+  top: $header-height;
+  left: $menu-width;
+  max-height: none !important;
+  max-width: none !important;
+  width: calc(100% - #{$menu-width});
+  height: calc(100vh - #{$header-height}) !important;
+
+  $nav-preview-vertical-padding: 40px;
+
+  .nav-preview {
+    @include nav-preview-medium();
+    padding-top: #{$nav-preview-vertical-padding / 2};
+    padding-bottom: #{$nav-preview-vertical-padding / 2};
+    padding-left: 0px;
+    padding-right: 0px;
+    position: absolute;
+    background-color: var(--mainBackgroundColor);
+    width: 100% !important;
+    border-top: none;
+    border-left: none;
+    border-right: none;
+
+    :last-child {
+      margin-right: $not-expanded-horizontal-margins;
+    }
+  }
+
+  ::ng-deep .tab-content {
+    @include content-preview-base();
+    background-color: var(--mainBackgroundColor);
+    scrollbar-color: var(--actionButtonColor) var(--mainBackgroundColor);
+  }
+
+  textarea,
+  ::ng-deep .tab-content {
+    max-height: none !important;
+    max-width: none !important;
+    margin-top: #{$nav-preview-tab-height + $nav-preview-vertical-padding} !important;
+    height: calc(100vh - #{$header-height + $nav-preview-tab-height + $nav-preview-vertical-padding}) !important;
+    width: 50% !important;
+    border: none !important;
+    border-radius: unset !important;
+  }
+
+  :host-context(.expanded) {
+    .root.maximized {
+      left: 0;
+      width: 100%;
+    }
+  }
+}
+
+@mixin maximized-in-small-view {
+  .root.maximized {
+    @include maximized-base();
+
+    textarea {
+      display: none;
     }
+
+    ::ng-deep .tab-content {
+      width: 100% !important;
+    }
+  }
+}
+
+@mixin maximized-tabs-in-mobile-view {
+  // Ellipsis on tabs for mobile view
+  .root.maximized {
+    .nav-preview {
+      ::ng-deep .nav-link {
+        @include ellipsis();
+
+        display: block !important;
+        max-width: 45% !important;
+        padding: 5px 0 !important;
+        margin-right: 10px !important;
+        text-align: center;
+
+        &:not(.active) {
+          max-width: 15% !important;
+        }
+
+        &.active {
+          padding: 5px 15px !important;
+        }
+      }
+    }
+  }
+}
+
+@mixin in-medium-view {
+  .root {
+    .nav-preview {
+      @include nav-preview-medium();
+    }
+
+    ::ng-deep .tab-content {
+      @include content-preview-base();
+      max-height: 210px;
+      border-bottom: 1px solid $input-border-color;
+      border-left: 1px solid $input-border-color;
+      border-right: 1px solid $input-border-color;
+      border-bottom-left-radius: $input-border-radius;
+      border-bottom-right-radius: $input-border-radius;
+    }
+  }
+}
+
+@mixin maximized-in-medium-view {
+  .root.maximized {
+    @include maximized-base();
+
+    textarea {
+      display: block;
+      padding: $base-padding;
+      border-right: 1px dashed $input-border-color !important;
+      resize: none;
+      scrollbar-color: var(--actionButtonColor) var(--textareaBackgroundColor);
+
+      &:focus {
+        box-shadow: none;
+      }
+    }
+  }
+}
+
+@include in-small-view();
+@include maximized-in-small-view();
+
+@media only screen and (max-width: $mobile-view) {
+  @include maximized-tabs-in-mobile-view();
+}
+
+@media only screen and (max-width: #{$mobile-view + $menu-width}) {
+  :host-context(.main-col:not(.expanded)) {
+    @include maximized-tabs-in-mobile-view();
+  }
+}
+
+@media only screen and (min-width: $small-view) {
+  :host-context(.expanded) {
+    @include in-medium-view();
+  }
+
+  @include maximized-in-medium-view();
+}
+
+@media only screen and (min-width: #{$small-view + $menu-width}) {
+  :host-context(.main-col:not(.expanded)) {
+    @include in-medium-view();
   }
 }

+ 25 - 12
client/src/app/shared/forms/markdown-textarea.component.ts

@@ -1,5 +1,5 @@
 import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
-import { Component, forwardRef, Input, OnInit } from '@angular/core'
+import { Component, forwardRef, Input, OnInit, ViewChild, ElementRef } from '@angular/core'
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
 import { Subject } from 'rxjs'
 import truncate from 'lodash-es/truncate'
@@ -22,18 +22,18 @@ import { MarkdownService } from '@app/shared/renderer'
 export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
   @Input() content = ''
   @Input() classes: string[] | { [klass: string]: any[] | any } = []
-  @Input() textareaWidth = '100%'
+  @Input() textareaMaxWidth = '100%'
   @Input() textareaHeight = '150px'
-  @Input() previewColumn = false
   @Input() truncate: number
   @Input() markdownType: 'text' | 'enhanced' = 'text'
   @Input() markdownVideo = false
   @Input() name = 'description'
 
-  textareaMarginRight = '0'
-  flexDirection = 'column'
+  @ViewChild('textarea') textareaElement: ElementRef
+
   truncatedPreviewHTML = ''
   previewHTML = ''
+  isMaximized = false
 
   private contentChanged = new Subject<string>()
 
@@ -51,11 +51,6 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
         .subscribe(() => this.updatePreviews())
 
     this.contentChanged.next(this.content)
-
-    if (this.previewColumn) {
-      this.flexDirection = 'row'
-      this.textareaMarginRight = '15px'
-    }
   }
 
   propagateChange = (_: any) => { /* empty */ }
@@ -80,8 +75,26 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
     this.contentChanged.next(this.content)
   }
 
-  arePreviewsDisplayed () {
-    return this.screenService.isInSmallView() === false
+  onMaximizeClick () {
+    this.isMaximized = !this.isMaximized
+
+    // Make sure textarea have the focus
+    this.textareaElement.nativeElement.focus()
+
+    // Make sure the window has no scrollbars
+    if (!this.isMaximized) {
+      this.unlockBodyScroll()
+    } else {
+      this.lockBodyScroll()
+    }
+  }
+
+  private lockBodyScroll () {
+    document.getElementById('content').classList.add('lock-scroll')
+  }
+
+  private unlockBodyScroll () {
+    document.getElementById('content').classList.remove('lock-scroll')
   }
 
   private async updatePreviews () {

+ 3 - 1
client/src/app/shared/images/global-icon.component.ts

@@ -54,7 +54,9 @@ const icons = {
   'users': require('!!raw-loader?!../../../assets/images/global/users.svg').default,
   'search': require('!!raw-loader?!../../../assets/images/global/search.svg').default,
   'refresh': require('!!raw-loader?!../../../assets/images/global/refresh.svg').default,
-  'npm': require('!!raw-loader?!../../../assets/images/global/npm.svg').default
+  'npm': require('!!raw-loader?!../../../assets/images/global/npm.svg').default,
+  'fullscreen': require('!!raw-loader?!../../../assets/images/global/fullscreen.svg').default,
+  'exit-fullscreen': require('!!raw-loader?!../../../assets/images/global/exit-fullscreen.svg').default
 }
 
 export type GlobalIconName = keyof typeof icons

+ 1 - 1
client/src/app/videos/+video-edit/shared/video-edit.component.html

@@ -232,7 +232,7 @@
               <label i18n for="support">Support</label>
               <my-help helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support you (membership platform...)."></my-help>
               <my-markdown-textarea
-                id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
+                id="support" formControlName="support" markdownType="enhanced"
                 [classes]="{ 'input-error': formErrors['support'] }"
               ></my-markdown-textarea>
               <div *ngIf="formErrors.support" class="form-error">

+ 1 - 13
client/src/app/videos/+video-edit/shared/video-edit.component.scss

@@ -161,18 +161,6 @@ p-calendar {
   }
 }
 
-::ng-deep my-markdown-textarea {
-  .root {
-    @include media-breakpoint-down(xl) {
-      flex-direction: column !important;
-    }
-
-    textarea {
-      max-width: 100%;
-    }
-  }
-}
-
 @include ng2-tags;
 
 // columns for the video
@@ -200,7 +188,7 @@ p-calendar {
   .col-video-edit {
     @include media-breakpoint-up(md) {
       @include make-col(8);
-  
+
       & + .col-video-edit {
         @include make-col(4);
       }

+ 16 - 0
client/src/assets/images/global/exit-fullscreen.svg

@@ -0,0 +1,16 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs/>
+  <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
+    <g id="Artboard-4" transform="translate(-400.000000, -1046.000000)" stroke="#333333" stroke-width="2">
+      <g id="Extras" transform="translate(48.000000, 1046.000000)">
+        <g id="exit-fullscreen" transform="translate(352.000000, 0.000000)">
+          <rect id="Rectangle-433" x="6" y="8" width="12" height="8"/>
+          <polyline id="Path-42" stroke-linecap="round" transform="translate(21.500000, 5.500000) scale(-1, -1) translate(-21.500000, -5.500000) " points="23 7 23 4 20 4"/>
+          <polyline id="Path-42" stroke-linecap="round" transform="translate(2.500000, 18.500000) scale(-1, -1) translate(-2.500000, -18.500000) " points="4 20 1 20 1 17"/>
+          <polyline id="Path-42" stroke-linecap="round" transform="translate(21.500000, 18.500000) scale(-1, 1) translate(-21.500000, -18.500000) " points="23 20 23 17 20 17"/>
+          <polyline id="Path-42" stroke-linecap="round" transform="translate(2.500000, 5.500000) scale(-1, 1) translate(-2.500000, -5.500000) " points="4 7 1 7 1 4"/>
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

+ 17 - 0
client/src/assets/images/global/fullscreen.svg

@@ -0,0 +1,17 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+  <title>fullscreen</title>
+  <desc>Created with Sketch.</desc>
+  <defs/>
+  <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="Artboard-4" transform="translate(-576.000000, -159.000000)" stroke="#333333" stroke-width="2">
+      <g id="33" transform="translate(576.000000, 159.000000)">
+        <rect id="Rectangle-433" x="1" y="4" width="22" height="16" rx="1"/>
+        <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" points="20 10 20 7 17 7"/>
+        <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" points="7 17 4 17 4 14"/>
+        <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" transform="translate(18.500000, 15.500000) scale(1, -1) translate(-18.500000, -15.500000) " points="20 17 20 14 17 14"/>
+        <polyline id="Path-42" stroke-linecap="round" stroke-linejoin="round" transform="translate(5.500000, 8.500000) scale(1, -1) translate(-5.500000, -8.500000) " points="7 10 4 10 4 7"/>
+      </g>
+    </g>
+  </g>
+</svg>

+ 18 - 0
client/src/sass/application.scss

@@ -38,6 +38,8 @@ body {
   --inputBackgroundColor: #{$input-background-color};
   --inputPlaceholderColor: #{$input-placeholder-color};
 
+  --textareaBackgroundColor: #{$textarea-background-color};
+
   --actionButtonColor: #{$grey-foreground-color};
   --supportButtonBackgroundColor: #{transparent};
   --supportButtonColor: #{var(--actionButtonColor)};
@@ -144,6 +146,16 @@ label {
       padding-right: $expanded-horizontal-margins;
     }
   }
+
+  &.lock-scroll .main-row > router-outlet + * {
+    // Lock and hide body scrollbars
+    position: fixed;
+
+    // Lock and hide sub-menu scrollbars
+    .sub-menu {
+      overflow-x: hidden;
+    }
+  }
 }
 
 .title-page {
@@ -304,6 +316,12 @@ table {
         margin-bottom: $sub-menu-margin-bottom-small-view;
       }
 
+      my-markdown-textarea {
+        .root {
+          max-width: 100% !important;
+        }
+      }
+
       input[type=text],
       input[type=password],
       input[type=email],

+ 4 - 0
client/src/sass/include/_variables.scss

@@ -68,6 +68,8 @@ $theater-bottom-space: 115px;
 $input-background-color: $bg-color;
 $input-placeholder-color: #898989;
 
+$textarea-background-color: $grey-background-hover-color;
+
 $sub-menu-margin-bottom: 30px;
 $sub-menu-margin-bottom-small-view: 10px;
 
@@ -95,6 +97,8 @@ $variables: (
   --inputBackgroundColor: var(--inputBackgroundColor),
   --inputPlaceholderColor: var(--inputPlaceholderColor),
 
+  --textareaBackgroundColor: var(--textareaBackgroundColor),
+
   --actionButtonColor: var(--actionButtonColor),
   --supportButtonColor: var(--supportButtonColor),
   --supportButtonBackgroundColor: var(--supportButtonBackgroundColor),