aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-02-20 16:13:05 +0100
committerChocobozzz <me@florianbigard.com>2018-02-20 16:13:55 +0100
commit07fa4c97ca50b83b0bee9230da97d02401b4e05f (patch)
tree07a5ef1b14d8f0596c241456e4cc772f38c0e4ba
parentdddf58c8ce58f6cdd4b8ba93690cceae8f52c37d (diff)
downloadPeerTube-07fa4c97ca50b83b0bee9230da97d02401b4e05f.tar.gz
PeerTube-07fa4c97ca50b83b0bee9230da97d02401b4e05f.tar.zst
PeerTube-07fa4c97ca50b83b0bee9230da97d02401b4e05f.zip
Add support to video support on client
-rw-r--r--client/src/app/about/about.component.ts4
-rw-r--r--client/src/app/app.component.scss5
-rw-r--r--client/src/app/app.component.ts2
-rw-r--r--client/src/app/header/header.component.scss4
-rw-r--r--client/src/app/shared/forms/form-validators/video.ts12
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.html10
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.scss1
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.ts37
-rw-r--r--client/src/app/shared/video/video-details.model.ts1
-rw-r--r--client/src/app/shared/video/video-edit.model.ts3
-rw-r--r--client/src/app/shared/video/video.service.ts1
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html13
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.scss4
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.ts5
-rw-r--r--client/src/app/videos/+video-edit/shared/video-image.component.scss2
-rw-r--r--client/src/app/videos/+video-edit/video-update.component.ts3
-rw-r--r--client/src/app/videos/+video-watch/modal/video-support.component.html22
-rw-r--r--client/src/app/videos/+video-watch/modal/video-support.component.scss3
-rw-r--r--client/src/app/videos/+video-watch/modal/video-support.component.ts36
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html8
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss23
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts8
-rw-r--r--client/src/app/videos/+video-watch/video-watch.module.ts2
-rw-r--r--client/src/app/videos/shared/markdown.service.ts38
-rw-r--r--client/src/assets/images/video/heart.svg13
-rw-r--r--client/src/sass/application.scss4
-rw-r--r--client/src/sass/video-js-custom.scss6
-rw-r--r--support/doc/dependencies.md6
28 files changed, 222 insertions, 54 deletions
diff --git a/client/src/app/about/about.component.ts b/client/src/app/about/about.component.ts
index 6a2e59be1..adad32b26 100644
--- a/client/src/app/about/about.component.ts
+++ b/client/src/app/about/about.component.ts
@@ -27,8 +27,8 @@ export class AboutComponent implements OnInit {
27 this.serverService.getAbout() 27 this.serverService.getAbout()
28 .subscribe( 28 .subscribe(
29 res => { 29 res => {
30 this.descriptionHTML = this.markdownService.markdownToHTML(res.instance.description) 30 this.descriptionHTML = this.markdownService.textMarkdownToHTML(res.instance.description)
31 this.termsHTML = this.markdownService.markdownToHTML(res.instance.terms) 31 this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms)
32 }, 32 },
33 33
34 err => this.notificationsService.error('Error', err) 34 err => this.notificationsService.error('Error', err)
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index 24c4f66c7..8e88bceff 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -49,7 +49,6 @@
49 49
50 #peertube-title { 50 #peertube-title {
51 @include disable-default-a-behaviour; 51 @include disable-default-a-behaviour;
52 width: 100%;
53 font-size: 20px; 52 font-size: 20px;
54 font-weight: $font-bold; 53 font-weight: $font-bold;
55 color: inherit !important; 54 color: inherit !important;
@@ -79,6 +78,10 @@
79 display: none; 78 display: none;
80 } 79 }
81 } 80 }
81
82 @media screen and (max-width: 350px) {
83 flex: auto;
84 }
82 } 85 }
83 86
84 .header-right { 87 .header-right {
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 220b104b7..3af33ba2b 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,5 +1,5 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router' 2import { GuardsCheckStart, Router } from '@angular/router'
3import { AuthService, ServerService } from '@app/core' 3import { AuthService, ServerService } from '@app/core'
4import { isInSmallView } from '@app/shared/misc/utils' 4import { isInSmallView } from '@app/shared/misc/utils'
5 5
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index 63b724cba..d79e6274b 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -14,7 +14,7 @@
14 width: calc(100% - 150px); 14 width: calc(100% - 150px);
15 } 15 }
16 16
17 @media screen and (max-width: 400px) { 17 @media screen and (max-width: 600px) {
18 width: calc(100% - 70px); 18 width: calc(100% - 70px);
19 } 19 }
20} 20}
@@ -50,7 +50,7 @@
50 margin-right: 6px; 50 margin-right: 6px;
51 } 51 }
52 52
53 @media screen and (max-width: 400px) { 53 @media screen and (max-width: 600px) {
54 margin-right: 10px; 54 margin-right: 10px;
55 padding: 0 10px; 55 padding: 0 10px;
56 56
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index 34a237a12..9ecbbbd60 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -44,10 +44,10 @@ export const VIDEO_CHANNEL = {
44} 44}
45 45
46export const VIDEO_DESCRIPTION = { 46export const VIDEO_DESCRIPTION = {
47 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(3000) ], 47 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ],
48 MESSAGES: { 48 MESSAGES: {
49 'minlength': 'Video description must be at least 3 characters long.', 49 'minlength': 'Video description must be at least 3 characters long.',
50 'maxlength': 'Video description cannot be more than 3000 characters long.' 50 'maxlength': 'Video description cannot be more than 10000 characters long.'
51 } 51 }
52} 52}
53 53
@@ -58,3 +58,11 @@ export const VIDEO_TAGS = {
58 'maxlength': 'A tag should be less than 30 characters long.' 58 'maxlength': 'A tag should be less than 30 characters long.'
59 } 59 }
60} 60}
61
62export const VIDEO_SUPPORT = {
63 VALIDATORS: [ Validators.minLength(3), Validators.maxLength(300) ],
64 MESSAGES: {
65 'minlength': 'Video support must be at least 3 characters long.',
66 'maxlength': 'Video support cannot be more than 300 characters long.'
67 }
68}
diff --git a/client/src/app/shared/forms/markdown-textarea.component.html b/client/src/app/shared/forms/markdown-textarea.component.html
index e8c5ded5b..46a97b163 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.html
+++ b/client/src/app/shared/forms/markdown-textarea.component.html
@@ -1,12 +1,12 @@
1<div class="root" [ngStyle]="{ 'flex-direction': flexDirection }"> 1<div class="root" [ngStyle]="{ 'flex-direction': flexDirection }">
2 <textarea 2 <textarea
3 [(ngModel)]="description" (ngModelChange)="onModelChange()" 3 [(ngModel)]="content" (ngModelChange)="onModelChange()"
4 [ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }" 4 [ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }"
5 id="description" name="description"> 5 id="description" name="description">
6 </textarea> 6 </textarea>
7 7
8 <tabset *ngIf="arePreviewsDisplayed()" class="previews"> 8 <tabset *ngIf="arePreviewsDisplayed()" class="previews">
9 <tab *ngIf="truncate !== undefined" heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab> 9 <tab *ngIf="truncate !== undefined" heading="Truncated preview" [innerHTML]="truncatedPreviewHTML"></tab>
10 <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab> 10 <tab heading="Complete preview" [innerHTML]="previewHTML"></tab>
11 </tabset> 11 </tabset>
12</div> 12</div>
diff --git a/client/src/app/shared/forms/markdown-textarea.component.scss b/client/src/app/shared/forms/markdown-textarea.component.scss
index 82aff541d..6f7494621 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.scss
+++ b/client/src/app/shared/forms/markdown-textarea.component.scss
@@ -22,6 +22,7 @@
22 min-height: 75px; 22 min-height: 75px;
23 padding: 15px; 23 padding: 15px;
24 font-size: 15px; 24 font-size: 15px;
25 word-wrap: break-word;
25 } 26 }
26 } 27 }
27} 28}
diff --git a/client/src/app/shared/forms/markdown-textarea.component.ts b/client/src/app/shared/forms/markdown-textarea.component.ts
index 2eae1b27e..928a63b28 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.ts
+++ b/client/src/app/shared/forms/markdown-textarea.component.ts
@@ -21,29 +21,30 @@ import truncate from 'lodash-es/truncate'
21}) 21})
22 22
23export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { 23export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
24 @Input() description = '' 24 @Input() content = ''
25 @Input() classes: string[] = [] 25 @Input() classes: string[] = []
26 @Input() textareaWidth = '100%' 26 @Input() textareaWidth = '100%'
27 @Input() textareaHeight = '150px' 27 @Input() textareaHeight = '150px'
28 @Input() previewColumn = false 28 @Input() previewColumn = false
29 @Input() truncate: number 29 @Input() truncate: number
30 @Input() markdownType: 'text' | 'enhanced' = 'text'
30 31
31 textareaMarginRight = '0' 32 textareaMarginRight = '0'
32 flexDirection = 'column' 33 flexDirection = 'column'
33 truncatedDescriptionHTML = '' 34 truncatedPreviewHTML = ''
34 descriptionHTML = '' 35 previewHTML = ''
35 36
36 private descriptionChanged = new Subject<string>() 37 private contentChanged = new Subject<string>()
37 38
38 constructor (private markdownService: MarkdownService) {} 39 constructor (private markdownService: MarkdownService) {}
39 40
40 ngOnInit () { 41 ngOnInit () {
41 this.descriptionChanged 42 this.contentChanged
42 .debounceTime(150) 43 .debounceTime(150)
43 .distinctUntilChanged() 44 .distinctUntilChanged()
44 .subscribe(() => this.updateDescriptionPreviews()) 45 .subscribe(() => this.updatePreviews())
45 46
46 this.descriptionChanged.next(this.description) 47 this.contentChanged.next(this.content)
47 48
48 if (this.previewColumn) { 49 if (this.previewColumn) {
49 this.flexDirection = 'row' 50 this.flexDirection = 'row'
@@ -54,9 +55,9 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
54 propagateChange = (_: any) => { /* empty */ } 55 propagateChange = (_: any) => { /* empty */ }
55 56
56 writeValue (description: string) { 57 writeValue (description: string) {
57 this.description = description 58 this.content = description
58 59
59 this.descriptionChanged.next(this.description) 60 this.contentChanged.next(this.content)
60 } 61 }
61 62
62 registerOnChange (fn: (_: any) => void) { 63 registerOnChange (fn: (_: any) => void) {
@@ -68,19 +69,25 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
68 } 69 }
69 70
70 onModelChange () { 71 onModelChange () {
71 this.propagateChange(this.description) 72 this.propagateChange(this.content)
72 73
73 this.descriptionChanged.next(this.description) 74 this.contentChanged.next(this.content)
74 } 75 }
75 76
76 arePreviewsDisplayed () { 77 arePreviewsDisplayed () {
77 return isInSmallView() === false 78 return isInSmallView() === false
78 } 79 }
79 80
80 private updateDescriptionPreviews () { 81 private updatePreviews () {
81 if (this.description === null || this.description === undefined) return 82 if (this.content === null || this.content === undefined) return
82 83
83 this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: this.truncate })) 84 this.truncatedPreviewHTML = this.markdownRender(truncate(this.content, { length: this.truncate }))
84 this.descriptionHTML = this.markdownService.markdownToHTML(this.description) 85 this.previewHTML = this.markdownRender(this.content)
86 }
87
88 private markdownRender (text: string) {
89 if (this.markdownType === 'text') return this.markdownService.textMarkdownToHTML(text)
90
91 return this.markdownService.enhancedMarkdownToHTML(text)
85 } 92 }
86} 93}
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index c746bfd66..4e4f64c7b 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -57,6 +57,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
57 this.channel = hash.channel 57 this.channel = hash.channel
58 this.account = hash.account 58 this.account = hash.account
59 this.tags = hash.tags 59 this.tags = hash.tags
60 this.support = hash.support
60 this.commentsEnabled = hash.commentsEnabled 61 this.commentsEnabled = hash.commentsEnabled
61 62
62 this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100 63 this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
index c39252f46..a8bbb63eb 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -12,6 +12,7 @@ export class VideoEdit {
12 commentsEnabled: boolean 12 commentsEnabled: boolean
13 channel: number 13 channel: number
14 privacy: VideoPrivacy 14 privacy: VideoPrivacy
15 support: string
15 thumbnailfile?: any 16 thumbnailfile?: any
16 previewfile?: any 17 previewfile?: any
17 thumbnailUrl: string 18 thumbnailUrl: string
@@ -33,6 +34,7 @@ export class VideoEdit {
33 this.commentsEnabled = videoDetails.commentsEnabled 34 this.commentsEnabled = videoDetails.commentsEnabled
34 this.channel = videoDetails.channel.id 35 this.channel = videoDetails.channel.id
35 this.privacy = videoDetails.privacy 36 this.privacy = videoDetails.privacy
37 this.support = videoDetails.support
36 this.thumbnailUrl = videoDetails.thumbnailUrl 38 this.thumbnailUrl = videoDetails.thumbnailUrl
37 this.previewUrl = videoDetails.previewUrl 39 this.previewUrl = videoDetails.previewUrl
38 } 40 }
@@ -50,6 +52,7 @@ export class VideoEdit {
50 licence: this.licence, 52 licence: this.licence,
51 language: this.language, 53 language: this.language,
52 description: this.description, 54 description: this.description,
55 support: this.support,
53 name: this.name, 56 name: this.name,
54 tags: this.tags, 57 tags: this.tags,
55 nsfw: this.nsfw, 58 nsfw: this.nsfw,
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 2e7138cd1..99da14779 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -62,6 +62,7 @@ export class VideoService {
62 tags: video.tags, 62 tags: video.tags,
63 nsfw: video.nsfw, 63 nsfw: video.nsfw,
64 commentsEnabled: video.commentsEnabled, 64 commentsEnabled: video.commentsEnabled,
65 support: video.support,
65 thumbnailfile: video.thumbnailfile, 66 thumbnailfile: video.thumbnailfile,
66 previewfile: video.previewfile 67 previewfile: video.previewfile
67 } 68 }
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index 899249778..bf2204013 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -111,7 +111,7 @@
111 </tab> 111 </tab>
112 112
113 <tab heading="Advanced settings"> 113 <tab heading="Advanced settings">
114 <div class="col-md-12"> 114 <div class="col-md-12 advanced-settings">
115 <div class="form-group"> 115 <div class="form-group">
116 <my-video-image 116 <my-video-image
117 inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile" 117 inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
@@ -125,6 +125,17 @@
125 previewWidth="360px" previewHeight="200px" 125 previewWidth="360px" previewHeight="200px"
126 ></my-video-image> 126 ></my-video-image>
127 </div> 127 </div>
128
129 <div class="form-group">
130 <label for="support">Support (markdown)</label>
131 <my-markdown-textarea
132 id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
133 [classes]="{ 'input-error': formErrors['support'] }"
134 ></my-markdown-textarea>
135 <div *ngIf="formErrors.support" class="form-error">
136 {{ formErrors.support }}
137 </div>
138 </div>
128 </div> 139 </div>
129 </tab> 140 </tab>
130 141
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
index f78336fa8..1317f7426 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
@@ -59,6 +59,10 @@
59 padding: 0 15px !important; 59 padding: 0 15px !important;
60 } 60 }
61 } 61 }
62
63 .advanced-settings .form-group {
64 margin-bottom: 20px;
65 }
62} 66}
63 67
64.submit-container { 68.submit-container {
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
index 85e5cc3f5..c26906551 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
@@ -1,7 +1,7 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { FormBuilder, FormControl, FormGroup } from '@angular/forms' 2import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { VIDEO_IMAGE } from '@app/shared' 4import { VIDEO_IMAGE, VIDEO_SUPPORT } from '@app/shared'
5import { NotificationsService } from 'angular2-notifications' 5import { NotificationsService } from 'angular2-notifications'
6import 'rxjs/add/observable/forkJoin' 6import 'rxjs/add/observable/forkJoin'
7import { ServerService } from '../../../core/server' 7import { ServerService } from '../../../core/server'
@@ -60,6 +60,7 @@ export class VideoEditComponent implements OnInit {
60 this.formErrors['description'] = '' 60 this.formErrors['description'] = ''
61 this.formErrors['thumbnailfile'] = '' 61 this.formErrors['thumbnailfile'] = ''
62 this.formErrors['previewfile'] = '' 62 this.formErrors['previewfile'] = ''
63 this.formErrors['support'] = ''
63 64
64 this.validationMessages['name'] = VIDEO_NAME.MESSAGES 65 this.validationMessages['name'] = VIDEO_NAME.MESSAGES
65 this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES 66 this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES
@@ -70,6 +71,7 @@ export class VideoEditComponent implements OnInit {
70 this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES 71 this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES
71 this.validationMessages['thumbnailfile'] = VIDEO_IMAGE.MESSAGES 72 this.validationMessages['thumbnailfile'] = VIDEO_IMAGE.MESSAGES
72 this.validationMessages['previewfile'] = VIDEO_IMAGE.MESSAGES 73 this.validationMessages['previewfile'] = VIDEO_IMAGE.MESSAGES
74 this.validationMessages['support'] = VIDEO_SUPPORT.MESSAGES
73 75
74 this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS)) 76 this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
75 this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS)) 77 this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
@@ -83,6 +85,7 @@ export class VideoEditComponent implements OnInit {
83 this.form.addControl('tags', new FormControl('')) 85 this.form.addControl('tags', new FormControl(''))
84 this.form.addControl('thumbnailfile', new FormControl('')) 86 this.form.addControl('thumbnailfile', new FormControl(''))
85 this.form.addControl('previewfile', new FormControl('')) 87 this.form.addControl('previewfile', new FormControl(''))
88 this.form.addControl('support', new FormControl(''))
86 } 89 }
87 90
88 ngOnInit () { 91 ngOnInit () {
diff --git a/client/src/app/videos/+video-edit/shared/video-image.component.scss b/client/src/app/videos/+video-edit/shared/video-image.component.scss
index c44d00b3c..98313536e 100644
--- a/client/src/app/videos/+video-edit/shared/video-image.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-image.component.scss
@@ -2,7 +2,7 @@
2@import '_mixins'; 2@import '_mixins';
3 3
4.root { 4.root {
5 height: 150px; 5 height: auto;
6 display: flex; 6 display: flex;
7 align-items: center; 7 align-items: center;
8 8
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts
index de34555ab..0ef3c0259 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -61,8 +61,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
61 .switchMap(video => { 61 .switchMap(video => {
62 return this.videoService 62 return this.videoService
63 .loadCompleteDescription(video.descriptionPath) 63 .loadCompleteDescription(video.descriptionPath)
64 .do(description => video.description = description) 64 .map(description => Object.assign(video, { description }))
65 .map(() => video)
66 }) 65 })
67 .subscribe( 66 .subscribe(
68 video => { 67 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
index 000000000..16ad9502a
--- /dev/null
+++ b/client/src/app/videos/+video-watch/modal/video-support.component.html
@@ -0,0 +1,22 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
2 <div class="modal-dialog">
3 <div class="modal-content">
4
5 <div class="modal-header">
6 <span class="close" aria-hidden="true" (click)="hide()"></span>
7 <h4 class="modal-title">Support</h4>
8 </div>
9
10 <div class="modal-body">
11
12 <div [innerHTML]="videoHTMLSupport"></div>
13
14 <div class="form-group inputs">
15 <span class="action-button action-button-cancel" (click)="hide()">
16 Cancel
17 </span>
18 </div>
19 </div>
20 </div>
21 </div>
22</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
index 000000000..184e09027
--- /dev/null
+++ b/client/src/app/videos/+video-watch/modal/video-support.component.scss
@@ -0,0 +1,3 @@
1.action-button-cancel {
2 margin-right: 0 !important;
3}
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
index 000000000..f805215b9
--- /dev/null
+++ b/client/src/app/videos/+video-watch/modal/video-support.component.ts
@@ -0,0 +1,36 @@
1import { Component, Input, ViewChild } from '@angular/core'
2import { MarkdownService } from '@app/videos/shared'
3
4import { ModalDirective } from 'ngx-bootstrap/modal'
5import { VideoDetails } from '../../../shared/video/video-details.model'
6
7@Component({
8 selector: 'my-video-support',
9 templateUrl: './video-support.component.html',
10 styleUrls: [ './video-support.component.scss' ]
11})
12export class VideoSupportComponent {
13 @Input() video: VideoDetails = null
14
15 @ViewChild('modal') modal: ModalDirective
16
17 videoHTMLSupport = ''
18
19 constructor (private markdownService: MarkdownService) {
20 // empty
21 }
22
23 show () {
24 this.modal.show()
25
26 if (this.video.support) {
27 this.videoHTMLSupport = this.markdownService.enhancedMarkdownToHTML(this.video.support)
28 } else {
29 this.videoHTMLSupport = ''
30 }
31 }
32
33 hide () {
34 this.modal.hide()
35 }
36}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 8c173d6b3..b8ec048b2 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -44,9 +44,14 @@
44 <span class="icon icon-dislike" title="Dislike this video"></span> 44 <span class="icon icon-dislike" title="Dislike this video"></span>
45 </div> 45 </div>
46 46
47 <div (click)="showSupportModal()" class="action-button action-button-support">
48 <span class="icon icon-support"></span>
49 <span class="icon-text">Support</span>
50 </div>
51
47 <div (click)="showShareModal()" class="action-button action-button-share"> 52 <div (click)="showShareModal()" class="action-button action-button-share">
48 <span class="icon icon-share"></span> 53 <span class="icon icon-share"></span>
49 Share 54 <span class="icon-text">Share</span>
50 </div> 55 </div>
51 56
52 <div class="action-more" dropdown dropup="true" placement="right"> 57 <div class="action-more" dropdown dropup="true" placement="right">
@@ -175,6 +180,7 @@
175</div> 180</div>
176 181
177<ng-template [ngIf]="video !== null"> 182<ng-template [ngIf]="video !== null">
183 <my-video-support #videoSupportModal [video]="video"></my-video-support>
178 <my-video-share #videoShareModal [video]="video"></my-video-share> 184 <my-video-share #videoShareModal [video]="video"></my-video-share>
179 <my-video-download #videoDownloadModal [video]="video"></my-video-download> 185 <my-video-download #videoDownloadModal [video]="video"></my-video-download>
180 <my-video-report #videoReportModal [video]="video"></my-video-report> 186 <my-video-report #videoReportModal [video]="video"></my-video-report>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index bc737ccd5..eb701e0ab 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -99,6 +99,7 @@
99 font-weight: $font-semibold; 99 font-weight: $font-semibold;
100 display: inline-block; 100 display: inline-block;
101 padding: 0 10px 0 10px; 101 padding: 0 10px 0 10px;
102 white-space: nowrap;
102 103
103 .icon { 104 .icon {
104 @include icon(21px); 105 @include icon(21px);
@@ -114,6 +115,10 @@
114 background-image: url('../../../assets/images/video/dislike-grey.svg'); 115 background-image: url('../../../assets/images/video/dislike-grey.svg');
115 } 116 }
116 117
118 &.icon-support {
119 background-image: url('../../../assets/images/video/heart.svg');
120 }
121
117 &.icon-share { 122 &.icon-share {
118 background-image: url('../../../assets/images/video/share.svg'); 123 background-image: url('../../../assets/images/video/share.svg');
119 } 124 }
@@ -249,11 +254,7 @@
249} 254}
250 255
251 256
252@media screen and (max-width: 1300px) { 257@media screen and (max-width: 1600px) {
253 .other-videos {
254 display: none;
255 }
256
257 .video-bottom { 258 .video-bottom {
258 .video-info { 259 .video-info {
259 margin-right: 0; 260 margin-right: 0;
@@ -288,6 +289,12 @@
288 } 289 }
289} 290}
290 291
292@media screen and (max-width: 1200px) {
293 .other-videos {
294 display: none;
295 }
296}
297
291@media screen and (max-width: 600px) { 298@media screen and (max-width: 600px) {
292 .video-bottom { 299 .video-bottom {
293 margin: 20px 0 0 0; 300 margin: 20px 0 0 0;
@@ -304,3 +311,9 @@
304 } 311 }
305 } 312 }
306} 313}
314
315@media screen and (max-width: 450px) {
316 .video-bottom .action-button .icon-text {
317 display: none !important;
318 }
319}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 553eed341..6b118b1de 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -1,5 +1,6 @@
1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' 1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
3import { MetaService } from '@ngx-meta/core' 4import { MetaService } from '@ngx-meta/core'
4import { NotificationsService } from 'angular2-notifications' 5import { NotificationsService } from 'angular2-notifications'
5import { Observable } from 'rxjs/Observable' 6import { Observable } from 'rxjs/Observable'
@@ -28,6 +29,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
28 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent 29 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
29 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent 30 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
30 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent 31 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
32 @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
31 33
32 otherVideosDisplayed: Video[] = [] 34 otherVideosDisplayed: Video[] = []
33 35
@@ -189,6 +191,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
189 this.videoReportModal.show() 191 this.videoReportModal.show()
190 } 192 }
191 193
194 showSupportModal () {
195 this.videoSupportModal.show()
196 }
197
192 showShareModal () { 198 showShareModal () {
193 this.videoShareModal.show() 199 this.videoShareModal.show()
194 } 200 }
@@ -264,7 +270,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
264 return 270 return
265 } 271 }
266 272
267 this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description) 273 this.videoHTMLDescription = this.markdownService.textMarkdownToHTML(this.video.description)
268 } 274 }
269 275
270 private setVideoLikesBarTooltipText () { 276 private setVideoLikesBarTooltipText () {
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts
index 085a9ec5a..6a22c36d9 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -1,4 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
2import { TooltipModule } from 'ngx-bootstrap/tooltip' 3import { TooltipModule } from 'ngx-bootstrap/tooltip'
3import { ClipboardModule } from 'ngx-clipboard' 4import { ClipboardModule } from 'ngx-clipboard'
4import { SharedModule } from '../../shared' 5import { SharedModule } from '../../shared'
@@ -29,6 +30,7 @@ import { VideoWatchComponent } from './video-watch.component'
29 VideoDownloadComponent, 30 VideoDownloadComponent,
30 VideoShareComponent, 31 VideoShareComponent,
31 VideoReportComponent, 32 VideoReportComponent,
33 VideoSupportComponent,
32 VideoCommentsComponent, 34 VideoCommentsComponent,
33 VideoCommentAddComponent, 35 VideoCommentAddComponent,
34 VideoCommentComponent 36 VideoCommentComponent
diff --git a/client/src/app/videos/shared/markdown.service.ts b/client/src/app/videos/shared/markdown.service.ts
index a275446eb..bd100f092 100644
--- a/client/src/app/videos/shared/markdown.service.ts
+++ b/client/src/app/videos/shared/markdown.service.ts
@@ -4,33 +4,51 @@ import * as MarkdownIt from 'markdown-it'
4 4
5@Injectable() 5@Injectable()
6export class MarkdownService { 6export class MarkdownService {
7 private markdownIt: MarkdownIt.MarkdownIt 7 private textMarkdownIt: MarkdownIt.MarkdownIt
8 private linkifier: MarkdownIt.MarkdownIt 8 private linkifier: MarkdownIt.MarkdownIt
9 private enhancedMarkdownIt: MarkdownIt.MarkdownIt
9 10
10 constructor () { 11 constructor () {
11 this.markdownIt = new MarkdownIt('zero', { linkify: true, breaks: true }) 12 this.textMarkdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
12 .enable('linkify') 13 .enable('linkify')
13 .enable('autolink') 14 .enable('autolink')
14 .enable('emphasis') 15 .enable('emphasis')
15 .enable('link') 16 .enable('link')
16 .enable('newline') 17 .enable('newline')
17 .enable('list') 18 .enable('list')
18 this.setTargetToLinks(this.markdownIt) 19 this.setTargetToLinks(this.textMarkdownIt)
20
21 this.enhancedMarkdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
22 .enable('linkify')
23 .enable('autolink')
24 .enable('emphasis')
25 .enable('link')
26 .enable('newline')
27 .enable('list')
28 .enable('image')
29 this.setTargetToLinks(this.enhancedMarkdownIt)
19 30
20 this.linkifier = new MarkdownIt('zero', { linkify: true }) 31 this.linkifier = new MarkdownIt('zero', { linkify: true })
21 .enable('linkify') 32 .enable('linkify')
22 this.setTargetToLinks(this.linkifier) 33 this.setTargetToLinks(this.linkifier)
23 } 34 }
24 35
25 markdownToHTML (markdown: string) { 36 textMarkdownToHTML (markdown: string) {
26 const html = this.markdownIt.render(markdown) 37 const html = this.textMarkdownIt.render(markdown)
27 38
28 // Avoid linkify truncated links 39 return this.avoidTruncatedLinks(html)
29 return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...') 40 }
41
42 enhancedMarkdownToHTML (markdown: string) {
43 const html = this.enhancedMarkdownIt.render(markdown)
44
45 return this.avoidTruncatedLinks(html)
30 } 46 }
31 47
32 linkify (text: string) { 48 linkify (text: string) {
33 return this.linkifier.render(text) 49 const html = this.linkifier.render(text)
50
51 return this.avoidTruncatedLinks(html)
34 } 52 }
35 53
36 private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) { 54 private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
@@ -53,4 +71,8 @@ export class MarkdownService {
53 return defaultRender(tokens, idx, options, env, self) 71 return defaultRender(tokens, idx, options, env, self)
54 } 72 }
55 } 73 }
74
75 private avoidTruncatedLinks (html) {
76 return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...')
77 }
56} 78}
diff --git a/client/src/assets/images/video/heart.svg b/client/src/assets/images/video/heart.svg
new file mode 100644
index 000000000..5d64aee0f
--- /dev/null
+++ b/client/src/assets/images/video/heart.svg
@@ -0,0 +1,13 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<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">
3 <defs></defs>
4 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5 <g id="Artboard-4" transform="translate(-48.000000, -1046.000000)" fill-rule="nonzero" fill="#585858">
6 <g id="Extras" transform="translate(48.000000, 1046.000000)">
7 <g id="heart">
8 <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>
9 </g>
10 </g>
11 </g>
12 </g>
13</svg>
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 80dd3408f..f04aef85d 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -24,6 +24,10 @@ body {
24 color: #000; 24 color: #000;
25} 25}
26 26
27strong {
28 font-weight: $font-semibold;
29}
30
27input.readonly { 31input.readonly {
28 /* Force blank on readonly inputs */ 32 /* Force blank on readonly inputs */
29 background-color: #fff !important; 33 background-color: #fff !important;
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss
index 209fb1683..ee6b9219b 100644
--- a/client/src/sass/video-js-custom.scss
+++ b/client/src/sass/video-js-custom.scss
@@ -24,7 +24,7 @@ $control-bar-height: 34px;
24 24
25 .vjs-big-play-button { 25 .vjs-big-play-button {
26 outline: 0; 26 outline: 0;
27 font-size: 7em; 27 font-size: 6em;
28 28
29 $big-play-width: 1.5em; 29 $big-play-width: 1.5em;
30 $big-play-height: 1em; 30 $big-play-height: 1em;
@@ -340,7 +340,7 @@ $control-bar-height: 34px;
340 340
341 @media screen and (max-width: 550px) { 341 @media screen and (max-width: 550px) {
342 .vjs-big-play-button { 342 .vjs-big-play-button {
343 font-size: 6.5em; 343 font-size: 5em;
344 } 344 }
345 345
346 .vjs-webtorrent { 346 .vjs-webtorrent {
@@ -358,7 +358,7 @@ $control-bar-height: 34px;
358 } 358 }
359 359
360 .vjs-big-play-button { 360 .vjs-big-play-button {
361 font-size: 5em; 361 font-size: 4em;
362 } 362 }
363 363
364 .vjs-volume-control { 364 .vjs-volume-control {
diff --git a/support/doc/dependencies.md b/support/doc/dependencies.md
index 4ced42b8b..b223b1368 100644
--- a/support/doc/dependencies.md
+++ b/support/doc/dependencies.md
@@ -10,7 +10,7 @@
10 10
11``` 11```
12$ sudo apt update 12$ sudo apt update
13$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server 13$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server git
14``` 14```
15 15
16## Arch Linux 16## Arch Linux
@@ -18,7 +18,7 @@ $ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server
18 1. Run: 18 1. Run:
19 19
20``` 20```
21$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis 21$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis git
22``` 22```
23 23
24## CentOS 7 24## CentOS 7
@@ -36,7 +36,7 @@ $ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis
36$ sudo yum update 36$ sudo yum update
37$ sudo yum install epel-release 37$ sudo yum install epel-release
38$ sudo yum update 38$ sudo yum update
39$ sudo yum install nginx postgresql postgresql-server openssl gcc make redis 39$ sudo yum install nginx postgresql postgresql-server openssl gcc make redis git
40``` 40```
41 41
42## Other distributions 42## Other distributions