Add support to video support on client
authorChocobozzz <me@florianbigard.com>
Tue, 20 Feb 2018 15:13:05 +0000 (16:13 +0100)
committerChocobozzz <me@florianbigard.com>
Tue, 20 Feb 2018 15:13:55 +0000 (16:13 +0100)
28 files changed:
client/src/app/about/about.component.ts
client/src/app/app.component.scss
client/src/app/app.component.ts
client/src/app/header/header.component.scss
client/src/app/shared/forms/form-validators/video.ts
client/src/app/shared/forms/markdown-textarea.component.html
client/src/app/shared/forms/markdown-textarea.component.scss
client/src/app/shared/forms/markdown-textarea.component.ts
client/src/app/shared/video/video-details.model.ts
client/src/app/shared/video/video-edit.model.ts
client/src/app/shared/video/video.service.ts
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.scss
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-edit/shared/video-image.component.scss
client/src/app/videos/+video-edit/video-update.component.ts
client/src/app/videos/+video-watch/modal/video-support.component.html [new file with mode: 0644]
client/src/app/videos/+video-watch/modal/video-support.component.scss [new file with mode: 0644]
client/src/app/videos/+video-watch/modal/video-support.component.ts [new file with mode: 0644]
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.scss
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/app/videos/+video-watch/video-watch.module.ts
client/src/app/videos/shared/markdown.service.ts
client/src/assets/images/video/heart.svg [new file with mode: 0644]
client/src/sass/application.scss
client/src/sass/video-js-custom.scss
support/doc/dependencies.md

index 6a2e59be18476b7762ddadffbdb30859e27cafff..adad32b263825ebd493b5fa9a0dac7b756c37b04 100644 (file)
@@ -27,8 +27,8 @@ export class AboutComponent implements OnInit {
     this.serverService.getAbout()
       .subscribe(
         res => {
-          this.descriptionHTML = this.markdownService.markdownToHTML(res.instance.description)
-          this.termsHTML = this.markdownService.markdownToHTML(res.instance.terms)
+          this.descriptionHTML = this.markdownService.textMarkdownToHTML(res.instance.description)
+          this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms)
         },
 
         err => this.notificationsService.error('Error', err)
index 24c4f66c72803ade539d38d29f4451febb41e064..8e88bceff3da58e75affa2ebe92b039ee21f8934 100644 (file)
@@ -49,7 +49,6 @@
 
     #peertube-title {
       @include disable-default-a-behaviour;
-      width: 100%;
       font-size: 20px;
       font-weight: $font-bold;
       color: inherit !important;
         display: none;
       }
     }
+
+    @media screen and (max-width: 350px) {
+      flex: auto;
+    }
   }
 
   .header-right {
index 220b104b7f12f9ebdc0969fd91a0c764216fbaa7..3af33ba2b47c8d1922b36edf13e07ec07bef0a4c 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router'
+import { GuardsCheckStart, Router } from '@angular/router'
 import { AuthService, ServerService } from '@app/core'
 import { isInSmallView } from '@app/shared/misc/utils'
 
index 63b724cbaa1252b065c1e46e48a7f48d7851692a..d79e6274b28f5107788d33af4d44e92dd25d7332 100644 (file)
@@ -14,7 +14,7 @@
     width: calc(100% - 150px);
   }
 
-  @media screen and (max-width: 400px) {
+  @media screen and (max-width: 600px) {
     width: calc(100% - 70px);
   }
 }
@@ -50,7 +50,7 @@
     margin-right: 6px;
   }
 
-  @media screen and (max-width: 400px) {
+  @media screen and (max-width: 600px) {
     margin-right: 10px;
     padding: 0 10px;
 
index 34a237a120116ff770f815fd1bf092884a3804d6..9ecbbbd6028c40e26f6bcea6574998eefca8dc71 100644 (file)
@@ -44,10 +44,10 @@ export const VIDEO_CHANNEL = {
 }
 
 export const VIDEO_DESCRIPTION = {
-  VALIDATORS: [ Validators.minLength(3), Validators.maxLength(3000) ],
+  VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ],
   MESSAGES: {
     'minlength': 'Video description must be at least 3 characters long.',
-    'maxlength': 'Video description cannot be more than 3000 characters long.'
+    'maxlength': 'Video description cannot be more than 10000 characters long.'
   }
 }
 
@@ -58,3 +58,11 @@ export const VIDEO_TAGS = {
     'maxlength': 'A tag should be less than 30 characters long.'
   }
 }
+
+export const VIDEO_SUPPORT = {
+  VALIDATORS: [ Validators.minLength(3), Validators.maxLength(300) ],
+  MESSAGES: {
+    'minlength': 'Video support must be at least 3 characters long.',
+    'maxlength': 'Video support cannot be more than 300 characters long.'
+  }
+}
index e8c5ded5b0c64d87da97e389bad971d79f48d480..46a97b163d42f3270e459e8347a343d887101ceb 100644 (file)
@@ -1,12 +1,12 @@
 <div class="root" [ngStyle]="{ 'flex-direction': flexDirection }">
   <textarea
-    [(ngModel)]="description" (ngModelChange)="onModelChange()"
-    [ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }"
-    id="description" name="description">
+      [(ngModel)]="content" (ngModelChange)="onModelChange()"
+      [ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }"
+      id="description" name="description">
   </textarea>
 
   <tabset *ngIf="arePreviewsDisplayed()" class="previews">
-    <tab *ngIf="truncate !== undefined" heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
-    <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
+    <tab *ngIf="truncate !== undefined" heading="Truncated preview" [innerHTML]="truncatedPreviewHTML"></tab>
+    <tab heading="Complete preview" [innerHTML]="previewHTML"></tab>
   </tabset>
 </div>
index 82aff541d0f279b01893b00427403151723f2df5..6f7494621f6a36c19b911e68148ec111725796cd 100644 (file)
@@ -22,6 +22,7 @@
       min-height: 75px;
       padding: 15px;
       font-size: 15px;
+      word-wrap: break-word;
     }
   }
 }
index 2eae1b27e07f8e8e2474e6eb30cbd527210bf57f..928a63b28115ecb4bd0c521b4dec84d4e89726d1 100644 (file)
@@ -21,29 +21,30 @@ import truncate from 'lodash-es/truncate'
 })
 
 export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
-  @Input() description = ''
+  @Input() content = ''
   @Input() classes: string[] = []
   @Input() textareaWidth = '100%'
   @Input() textareaHeight = '150px'
   @Input() previewColumn = false
   @Input() truncate: number
+  @Input() markdownType: 'text' | 'enhanced' = 'text'
 
   textareaMarginRight = '0'
   flexDirection = 'column'
-  truncatedDescriptionHTML = ''
-  descriptionHTML = ''
+  truncatedPreviewHTML = ''
+  previewHTML = ''
 
-  private descriptionChanged = new Subject<string>()
+  private contentChanged = new Subject<string>()
 
   constructor (private markdownService: MarkdownService) {}
 
   ngOnInit () {
-    this.descriptionChanged
+    this.contentChanged
       .debounceTime(150)
       .distinctUntilChanged()
-      .subscribe(() => this.updateDescriptionPreviews())
+      .subscribe(() => this.updatePreviews())
 
-    this.descriptionChanged.next(this.description)
+    this.contentChanged.next(this.content)
 
     if (this.previewColumn) {
       this.flexDirection = 'row'
@@ -54,9 +55,9 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
   propagateChange = (_: any) => { /* empty */ }
 
   writeValue (description: string) {
-    this.description = description
+    this.content = description
 
-    this.descriptionChanged.next(this.description)
+    this.contentChanged.next(this.content)
   }
 
   registerOnChange (fn: (_: any) => void) {
@@ -68,19 +69,25 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
   }
 
   onModelChange () {
-    this.propagateChange(this.description)
+    this.propagateChange(this.content)
 
-    this.descriptionChanged.next(this.description)
+    this.contentChanged.next(this.content)
   }
 
   arePreviewsDisplayed () {
     return isInSmallView() === false
   }
 
-  private updateDescriptionPreviews () {
-    if (this.description === null || this.description === undefined) return
+  private updatePreviews () {
+    if (this.content === null || this.content === undefined) return
 
-    this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: this.truncate }))
-    this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
+    this.truncatedPreviewHTML = this.markdownRender(truncate(this.content, { length: this.truncate }))
+    this.previewHTML = this.markdownRender(this.content)
+  }
+
+  private markdownRender (text: string) {
+    if (this.markdownType === 'text') return this.markdownService.textMarkdownToHTML(text)
+
+    return this.markdownService.enhancedMarkdownToHTML(text)
   }
 }
index c746bfd6630d6e533c8678b1900f484fcbdebb74..4e4f64c7bf22470794785ca8dc86c568dd264eaf 100644 (file)
@@ -57,6 +57,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
     this.channel = hash.channel
     this.account = hash.account
     this.tags = hash.tags
+    this.support = hash.support
     this.commentsEnabled = hash.commentsEnabled
 
     this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
index c39252f46fffa527b81381ef206aa273fb62bcdd..a8bbb63eb2c48b37422d26e4249765c84cedc977 100644 (file)
@@ -12,6 +12,7 @@ export class VideoEdit {
   commentsEnabled: boolean
   channel: number
   privacy: VideoPrivacy
+  support: string
   thumbnailfile?: any
   previewfile?: any
   thumbnailUrl: string
@@ -33,6 +34,7 @@ export class VideoEdit {
       this.commentsEnabled = videoDetails.commentsEnabled
       this.channel = videoDetails.channel.id
       this.privacy = videoDetails.privacy
+      this.support = videoDetails.support
       this.thumbnailUrl = videoDetails.thumbnailUrl
       this.previewUrl = videoDetails.previewUrl
     }
@@ -50,6 +52,7 @@ export class VideoEdit {
       licence: this.licence,
       language: this.language,
       description: this.description,
+      support: this.support,
       name: this.name,
       tags: this.tags,
       nsfw: this.nsfw,
index 2e7138cd1fe187a6b16ad9249ff74aa554b600de..99da147798d26c15e21b14984448646865234ee5 100644 (file)
@@ -62,6 +62,7 @@ export class VideoService {
       tags: video.tags,
       nsfw: video.nsfw,
       commentsEnabled: video.commentsEnabled,
+      support: video.support,
       thumbnailfile: video.thumbnailfile,
       previewfile: video.previewfile
     }
index 899249778f4be37b17f8abbda397619e7f10b619..bf2204013efe1ee7ad661f01fe767782c9db33e6 100644 (file)
     </tab>
 
     <tab heading="Advanced settings">
-      <div class="col-md-12">
+      <div class="col-md-12 advanced-settings">
         <div class="form-group">
           <my-video-image
             inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
             previewWidth="360px" previewHeight="200px"
           ></my-video-image>
         </div>
+
+        <div class="form-group">
+          <label for="support">Support (markdown)</label>
+          <my-markdown-textarea
+              id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
+              [classes]="{ 'input-error': formErrors['support'] }"
+          ></my-markdown-textarea>
+          <div *ngIf="formErrors.support" class="form-error">
+            {{ formErrors.support }}
+          </div>
+        </div>
       </div>
     </tab>
 
index f78336fa8f226784861968d1b7af7c2d084fd695..1317f74264f2992bce3b2adcba3c7e83ddb76aff 100644 (file)
       padding: 0 15px !important;
     }
   }
+
+  .advanced-settings .form-group {
+    margin-bottom: 20px;
+  }
 }
 
 .submit-container {
index 85e5cc3f596125be76242af72f6049fa197a6903..c2690655160af2df285dabf9328bd28899b6922e 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, Input, OnInit } from '@angular/core'
 import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
 import { ActivatedRoute, Router } from '@angular/router'
-import { VIDEO_IMAGE } from '@app/shared'
+import { VIDEO_IMAGE, VIDEO_SUPPORT } from '@app/shared'
 import { NotificationsService } from 'angular2-notifications'
 import 'rxjs/add/observable/forkJoin'
 import { ServerService } from '../../../core/server'
@@ -60,6 +60,7 @@ export class VideoEditComponent implements OnInit {
     this.formErrors['description'] = ''
     this.formErrors['thumbnailfile'] = ''
     this.formErrors['previewfile'] = ''
+    this.formErrors['support'] = ''
 
     this.validationMessages['name'] = VIDEO_NAME.MESSAGES
     this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES
@@ -70,6 +71,7 @@ export class VideoEditComponent implements OnInit {
     this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES
     this.validationMessages['thumbnailfile'] = VIDEO_IMAGE.MESSAGES
     this.validationMessages['previewfile'] = VIDEO_IMAGE.MESSAGES
+    this.validationMessages['support'] = VIDEO_SUPPORT.MESSAGES
 
     this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
     this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
@@ -83,6 +85,7 @@ export class VideoEditComponent implements OnInit {
     this.form.addControl('tags', new FormControl(''))
     this.form.addControl('thumbnailfile', new FormControl(''))
     this.form.addControl('previewfile', new FormControl(''))
+    this.form.addControl('support', new FormControl(''))
   }
 
   ngOnInit () {
index c44d00b3c805c0d4f333977aa1ef650beec2dfed..98313536ecfe3a0952288aafc614551e67aa69fa 100644 (file)
@@ -2,7 +2,7 @@
 @import '_mixins';
 
 .root {
-  height: 150px;
+  height: auto;
   display: flex;
   align-items: center;
 
index de34555abc47a5f73c1582e4200993497bd6f8fb..0ef3c02591def381d8eae000d690fc089a3e736f 100644 (file)
@@ -61,8 +61,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
       .switchMap(video => {
         return this.videoService
           .loadCompleteDescription(video.descriptionPath)
-          .do(description => video.description = description)
-          .map(() => video)
+          .map(description => Object.assign(video,  { description }))
       })
       .subscribe(
         video => {
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.html b/client/src/app/videos/+video-watch/modal/video-support.component.html
new file mode 100644 (file)
index 0000000..16ad950
--- /dev/null
@@ -0,0 +1,22 @@
+<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
+  <div class="modal-dialog">
+    <div class="modal-content">
+
+      <div class="modal-header">
+        <span class="close" aria-hidden="true" (click)="hide()"></span>
+        <h4 class="modal-title">Support</h4>
+      </div>
+
+      <div class="modal-body">
+
+        <div [innerHTML]="videoHTMLSupport"></div>
+
+        <div class="form-group inputs">
+          <span class="action-button action-button-cancel" (click)="hide()">
+            Cancel
+          </span>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.scss b/client/src/app/videos/+video-watch/modal/video-support.component.scss
new file mode 100644 (file)
index 0000000..184e090
--- /dev/null
@@ -0,0 +1,3 @@
+.action-button-cancel {
+  margin-right: 0 !important;
+}
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.ts b/client/src/app/videos/+video-watch/modal/video-support.component.ts
new file mode 100644 (file)
index 0000000..f805215
--- /dev/null
@@ -0,0 +1,36 @@
+import { Component, Input, ViewChild } from '@angular/core'
+import { MarkdownService } from '@app/videos/shared'
+
+import { ModalDirective } from 'ngx-bootstrap/modal'
+import { VideoDetails } from '../../../shared/video/video-details.model'
+
+@Component({
+  selector: 'my-video-support',
+  templateUrl: './video-support.component.html',
+  styleUrls: [ './video-support.component.scss' ]
+})
+export class VideoSupportComponent {
+  @Input() video: VideoDetails = null
+
+  @ViewChild('modal') modal: ModalDirective
+
+  videoHTMLSupport = ''
+
+  constructor (private markdownService: MarkdownService) {
+    // empty
+  }
+
+  show () {
+    this.modal.show()
+
+    if (this.video.support) {
+      this.videoHTMLSupport = this.markdownService.enhancedMarkdownToHTML(this.video.support)
+    } else {
+      this.videoHTMLSupport = ''
+    }
+  }
+
+  hide () {
+    this.modal.hide()
+  }
+}
index 8c173d6b35fcdd0a6608b1da3bbd2869f404472a..b8ec048b25c870b99c2972499bf6a20790411524 100644 (file)
               <span class="icon icon-dislike" title="Dislike this video"></span>
             </div>
 
+            <div (click)="showSupportModal()" class="action-button action-button-support">
+              <span class="icon icon-support"></span>
+              <span class="icon-text">Support</span>
+            </div>
+
             <div (click)="showShareModal()" class="action-button action-button-share">
               <span class="icon icon-share"></span>
-              Share
+              <span class="icon-text">Share</span>
             </div>
 
             <div class="action-more" dropdown dropup="true" placement="right">
 </div>
 
 <ng-template [ngIf]="video !== null">
+  <my-video-support #videoSupportModal [video]="video"></my-video-support>
   <my-video-share #videoShareModal [video]="video"></my-video-share>
   <my-video-download #videoDownloadModal [video]="video"></my-video-download>
   <my-video-report #videoReportModal [video]="video"></my-video-report>
index bc737ccd5482425083abf0cf0be41275daef2030..eb701e0abac6d51587f1a6b24536680ce11edcef 100644 (file)
@@ -99,6 +99,7 @@
             font-weight: $font-semibold;
             display: inline-block;
             padding: 0 10px 0 10px;
+            white-space: nowrap;
 
             .icon {
               @include icon(21px);
                 background-image: url('../../../assets/images/video/dislike-grey.svg');
               }
 
+              &.icon-support {
+                background-image: url('../../../assets/images/video/heart.svg');
+              }
+
               &.icon-share {
                 background-image: url('../../../assets/images/video/share.svg');
               }
 }
 
 
-@media screen and (max-width: 1300px) {
-  .other-videos {
-    display: none;
-  }
-
+@media screen and (max-width: 1600px) {
   .video-bottom {
     .video-info {
       margin-right: 0;
   }
 }
 
+@media screen and (max-width: 1200px) {
+  .other-videos {
+    display: none;
+  }
+}
+
 @media screen and (max-width: 600px) {
   .video-bottom {
     margin: 20px 0 0 0;
     }
   }
 }
+
+@media screen and (max-width: 450px) {
+  .video-bottom  .action-button .icon-text {
+    display: none !important;
+  }
+}
index 553eed34139f897b27e48674a90a992850db8dfa..6b118b1de2bfddc2c6ffa725447d14b71b109233 100644 (file)
@@ -1,5 +1,6 @@
 import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
+import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { MetaService } from '@ngx-meta/core'
 import { NotificationsService } from 'angular2-notifications'
 import { Observable } from 'rxjs/Observable'
@@ -28,6 +29,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
   @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
+  @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
 
   otherVideosDisplayed: Video[] = []
 
@@ -189,6 +191,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.videoReportModal.show()
   }
 
+  showSupportModal () {
+    this.videoSupportModal.show()
+  }
+
   showShareModal () {
     this.videoShareModal.show()
   }
@@ -264,7 +270,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       return
     }
 
-    this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
+    this.videoHTMLDescription = this.markdownService.textMarkdownToHTML(this.video.description)
   }
 
   private setVideoLikesBarTooltipText () {
index 085a9ec5a36a15e078a9a6682c97c3950bd62256..6a22c36d9ae66e95ad9f68572830dcea9bd81f32 100644 (file)
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { TooltipModule } from 'ngx-bootstrap/tooltip'
 import { ClipboardModule } from 'ngx-clipboard'
 import { SharedModule } from '../../shared'
@@ -29,6 +30,7 @@ import { VideoWatchComponent } from './video-watch.component'
     VideoDownloadComponent,
     VideoShareComponent,
     VideoReportComponent,
+    VideoSupportComponent,
     VideoCommentsComponent,
     VideoCommentAddComponent,
     VideoCommentComponent
index a275446eba89aa0c2ee95b625f87f6c526d1b575..bd100f0926dcc88b95998f1e1e606dfa27aed3e1 100644 (file)
@@ -4,33 +4,51 @@ import * as MarkdownIt from 'markdown-it'
 
 @Injectable()
 export class MarkdownService {
-  private markdownIt: MarkdownIt.MarkdownIt
+  private textMarkdownIt: MarkdownIt.MarkdownIt
   private linkifier: MarkdownIt.MarkdownIt
+  private enhancedMarkdownIt: MarkdownIt.MarkdownIt
 
   constructor () {
-    this.markdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
+    this.textMarkdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
       .enable('linkify')
       .enable('autolink')
       .enable('emphasis')
       .enable('link')
       .enable('newline')
       .enable('list')
-    this.setTargetToLinks(this.markdownIt)
+    this.setTargetToLinks(this.textMarkdownIt)
+
+    this.enhancedMarkdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
+      .enable('linkify')
+      .enable('autolink')
+      .enable('emphasis')
+      .enable('link')
+      .enable('newline')
+      .enable('list')
+      .enable('image')
+    this.setTargetToLinks(this.enhancedMarkdownIt)
 
     this.linkifier = new MarkdownIt('zero', { linkify: true })
       .enable('linkify')
     this.setTargetToLinks(this.linkifier)
   }
 
-  markdownToHTML (markdown: string) {
-    const html = this.markdownIt.render(markdown)
+  textMarkdownToHTML (markdown: string) {
+    const html = this.textMarkdownIt.render(markdown)
 
-    // Avoid linkify truncated links
-    return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...')
+    return this.avoidTruncatedLinks(html)
+  }
+
+  enhancedMarkdownToHTML (markdown: string) {
+    const html = this.enhancedMarkdownIt.render(markdown)
+
+    return this.avoidTruncatedLinks(html)
   }
 
   linkify (text: string) {
-    return this.linkifier.render(text)
+    const html = this.linkifier.render(text)
+
+    return this.avoidTruncatedLinks(html)
   }
 
   private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
@@ -53,4 +71,8 @@ export class MarkdownService {
       return defaultRender(tokens, idx, options, env, self)
     }
   }
+
+  private avoidTruncatedLinks (html) {
+    return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...')
+  }
 }
diff --git a/client/src/assets/images/video/heart.svg b/client/src/assets/images/video/heart.svg
new file mode 100644 (file)
index 0000000..5d64aee
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Artboard-4" transform="translate(-48.000000, -1046.000000)" fill-rule="nonzero" fill="#585858">
+            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+                <g id="heart">
+                    <path d="M12.0174466,21 L20.9041801,11.3556763 C22.6291961,9.13778099 22.2795957,5.90145416 20.1233257,4.12713796 C17.9670557,2.35282175 14.8206518,2.71241362 13.0956358,4.93030888 L12.0174465,6.5 L10.9043642,4.93030888 C9.17934824,2.71241362 6.0329443,2.35282175 3.87667432,4.12713796 C1.72040435,5.90145416 1.37080391,9.13778099 3.09581989,11.3556763 L12.0174466,21 Z"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
index 80dd3408f223c3dc7d444d643df101099e5dd75e..f04aef85d89db17b8f84796e4a37123e8a4c3cd4 100644 (file)
@@ -24,6 +24,10 @@ body {
   color: #000;
 }
 
+strong {
+  font-weight: $font-semibold;
+}
+
 input.readonly {
   /* Force blank on readonly inputs */
   background-color: #fff !important;
index 209fb16831c209ac2759a112b816d7362af731a3..ee6b9219bc36a02693a011d968e700e726b58fe1 100644 (file)
@@ -24,7 +24,7 @@ $control-bar-height: 34px;
 
   .vjs-big-play-button {
     outline: 0;
-    font-size: 7em;
+    font-size: 6em;
 
     $big-play-width: 1.5em;
     $big-play-height: 1em;
@@ -340,7 +340,7 @@ $control-bar-height: 34px;
 
   @media screen and (max-width: 550px) {
     .vjs-big-play-button {
-      font-size: 6.5em;
+      font-size: 5em;
     }
 
     .vjs-webtorrent {
@@ -358,7 +358,7 @@ $control-bar-height: 34px;
     }
 
     .vjs-big-play-button {
-      font-size: 5em;
+      font-size: 4em;
     }
 
     .vjs-volume-control {
index 4ced42b8b4bcf52a24b4839f535384b90daac80d..b223b13681011cf672e1f3d0776921e9aca095a7 100644 (file)
@@ -10,7 +10,7 @@
 
 ```
 $ sudo apt update
-$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server
+$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server git
 ```
 
 ## Arch Linux
@@ -18,7 +18,7 @@ $ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server
   1. Run:
 
 ```
-$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis
+$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis git
 ```
 
 ## CentOS 7
@@ -36,7 +36,7 @@ $ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis
 $ sudo yum update
 $ sudo yum install epel-release
 $ sudo yum update
-$ sudo yum install nginx postgresql postgresql-server openssl gcc make redis
+$ sudo yum install nginx postgresql postgresql-server openssl gcc make redis git
 ```
 
 ## Other distributions