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>
pull/2701/head
Kim 2020-04-28 14:53:43 +02:00 committed by GitHub
parent 4682468d4d
commit b15fe00f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 338 additions and 78 deletions

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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"

View File

@ -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);
}

View File

@ -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>

View File

@ -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;
textarea {
@include peertube-textarea(100%, 150px);
@mixin in-small-view {
.root {
display: flex;
flex-direction: column;
margin-bottom: 15px;
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;
}
.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;
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;
}
}
}
}
.previews {
max-height: 150px;
overflow-y: auto;
flex-grow: 1;
@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);
: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();
}
}

View File

@ -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 () {

View File

@ -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

View File

@ -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">

View File

@ -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);
}

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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],

View File

@ -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),