aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-10-30 20:26:06 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-10-30 20:26:06 +0100
commit2de96f4d6b800076743ed2073f9529816cfd5c8a (patch)
tree8c2dbd29fe5560e0b24edaa58038581bddd49778 /client
parent8bf89b095a711d5ac5e6ef55575b166257d0d526 (diff)
downloadPeerTube-2de96f4d6b800076743ed2073f9529816cfd5c8a.tar.gz
PeerTube-2de96f4d6b800076743ed2073f9529816cfd5c8a.tar.zst
PeerTube-2de96f4d6b800076743ed2073f9529816cfd5c8a.zip
Lazy description and previews to video form
Diffstat (limited to 'client')
-rw-r--r--client/.bootstraprc2
-rw-r--r--client/src/app/core/auth/auth.service.ts24
-rw-r--r--client/src/app/shared/forms/form-validators/video.ts4
-rw-r--r--client/src/app/videos/+video-edit/video-add.component.html8
-rw-r--r--client/src/app/videos/+video-edit/video-add.component.ts20
-rw-r--r--client/src/app/videos/+video-edit/video-add.module.ts11
-rw-r--r--client/src/app/videos/+video-edit/video-edit.module.ts33
-rw-r--r--client/src/app/videos/+video-edit/video-update.component.html9
-rw-r--r--client/src/app/videos/+video-edit/video-update.component.ts33
-rw-r--r--client/src/app/videos/+video-edit/video-update.module.ts11
-rw-r--r--client/src/app/videos/+video-watch/video-report.component.html2
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html10
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss12
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts45
-rw-r--r--client/src/app/videos/shared/index.ts1
-rw-r--r--client/src/app/videos/shared/video-description.component.html9
-rw-r--r--client/src/app/videos/shared/video-description.component.scss15
-rw-r--r--client/src/app/videos/shared/video-description.component.ts68
-rw-r--r--client/src/app/videos/shared/video-details.model.ts2
-rw-r--r--client/src/app/videos/shared/video.service.ts14
-rw-r--r--client/tslint.json2
21 files changed, 266 insertions, 69 deletions
diff --git a/client/.bootstraprc b/client/.bootstraprc
index e560cb5fb..6ceef4fe9 100644
--- a/client/.bootstraprc
+++ b/client/.bootstraprc
@@ -81,7 +81,7 @@ styles:
81 dropdowns: true 81 dropdowns: true
82 button-groups: true 82 button-groups: true
83 input-groups: true 83 input-groups: true
84 navs: false 84 navs: true
85 navbar: false 85 navbar: false
86 breadcrumbs: false 86 breadcrumbs: false
87 pagination: true 87 pagination: true
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index df6e5135b..913c857e3 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -3,6 +3,8 @@ import { Router } from '@angular/router'
3import { Observable } from 'rxjs/Observable' 3import { Observable } from 'rxjs/Observable'
4import { Subject } from 'rxjs/Subject' 4import { Subject } from 'rxjs/Subject'
5import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
6import { ReplaySubject } from 'rxjs/ReplaySubject'
7import 'rxjs/add/operator/do'
6import 'rxjs/add/operator/map' 8import 'rxjs/add/operator/map'
7import 'rxjs/add/operator/mergeMap' 9import 'rxjs/add/operator/mergeMap'
8import 'rxjs/add/observable/throw' 10import 'rxjs/add/observable/throw'
@@ -54,6 +56,7 @@ export class AuthService {
54 private static BASE_USER_INFORMATION_URL = API_URL + '/api/v1/users/me' 56 private static BASE_USER_INFORMATION_URL = API_URL + '/api/v1/users/me'
55 57
56 loginChangedSource: Observable<AuthStatus> 58 loginChangedSource: Observable<AuthStatus>
59 userInformationLoaded = new ReplaySubject<boolean>(1)
57 60
58 private clientId: string 61 private clientId: string
59 private clientSecret: string 62 private clientSecret: string
@@ -199,16 +202,17 @@ export class AuthService {
199 } 202 }
200 203
201 this.mergeUserInformation(obj) 204 this.mergeUserInformation(obj)
202 .subscribe( 205 .do(() => this.userInformationLoaded.next(true))
203 res => { 206 .subscribe(
204 this.user.displayNSFW = res.displayNSFW 207 res => {
205 this.user.role = res.role 208 this.user.displayNSFW = res.displayNSFW
206 this.user.videoChannels = res.videoChannels 209 this.user.role = res.role
207 this.user.author = res.author 210 this.user.videoChannels = res.videoChannels
208 211 this.user.author = res.author
209 this.user.save() 212
210 } 213 this.user.save()
211 ) 214 }
215 )
212 } 216 }
213 217
214 private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { 218 private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> {
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index 286a11179..434773501 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -36,11 +36,11 @@ export const VIDEO_CHANNEL = {
36} 36}
37 37
38export const VIDEO_DESCRIPTION = { 38export const VIDEO_DESCRIPTION = {
39 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ], 39 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(3000) ],
40 MESSAGES: { 40 MESSAGES: {
41 'required': 'Video description is required.', 41 'required': 'Video description is required.',
42 'minlength': 'Video description must be at least 3 characters long.', 42 'minlength': 'Video description must be at least 3 characters long.',
43 'maxlength': 'Video description cannot be more than 250 characters long.' 43 'maxlength': 'Video description cannot be more than 3000 characters long.'
44 } 44 }
45} 45}
46 46
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html
index 3bf4101f4..a70788ed8 100644
--- a/client/src/app/videos/+video-edit/video-add.component.html
+++ b/client/src/app/videos/+video-edit/video-add.component.html
@@ -28,7 +28,6 @@
28 <div class="form-group"> 28 <div class="form-group">
29 <label for="category">Channel</label> 29 <label for="category">Channel</label>
30 <select class="form-control" id="channelId" formControlName="channelId"> 30 <select class="form-control" id="channelId" formControlName="channelId">
31 <option></option>
32 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> 31 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
33 </select> 32 </select>
34 33
@@ -103,11 +102,8 @@
103 102
104 <div class="form-group"> 103 <div class="form-group">
105 <label for="description">Description</label> 104 <label for="description">Description</label>
106 <textarea 105 <my-video-description formControlName="description"></my-video-description>
107 id="description" class="form-control" placeholder="Description..." 106
108 formControlName="description"
109 >
110 </textarea>
111 <div *ngIf="formErrors.description" class="alert alert-danger"> 107 <div *ngIf="formErrors.description" class="alert alert-danger">
112 {{ formErrors.description }} 108 {{ formErrors.description }}
113 </div> 109 </div>
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 92b03e8c9..5b5557ed9 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -82,7 +82,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
82 category: [ '', VIDEO_CATEGORY.VALIDATORS ], 82 category: [ '', VIDEO_CATEGORY.VALIDATORS ],
83 licence: [ '', VIDEO_LICENCE.VALIDATORS ], 83 licence: [ '', VIDEO_LICENCE.VALIDATORS ],
84 language: [ '', VIDEO_LANGUAGE.VALIDATORS ], 84 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
85 channelId: [ this.userVideoChannels[0].id, VIDEO_CHANNEL.VALIDATORS ], 85 channelId: [ '', VIDEO_CHANNEL.VALIDATORS ],
86 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], 86 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
87 videofile: [ '', VIDEO_FILE.VALIDATORS ], 87 videofile: [ '', VIDEO_FILE.VALIDATORS ],
88 tags: [ '' ] 88 tags: [ '' ]
@@ -96,10 +96,22 @@ export class VideoAddComponent extends FormReactive implements OnInit {
96 this.videoLicences = this.serverService.getVideoLicences() 96 this.videoLicences = this.serverService.getVideoLicences()
97 this.videoLanguages = this.serverService.getVideoLanguages() 97 this.videoLanguages = this.serverService.getVideoLanguages()
98 98
99 const user = this.authService.getUser()
100 this.userVideoChannels = user.videoChannels.map(v => ({ id: v.id, label: v.name }))
101
102 this.buildForm() 99 this.buildForm()
100
101 this.authService.userInformationLoaded
102 .subscribe(
103 () => {
104 const user = this.authService.getUser()
105 if (!user) return
106
107 const videoChannels = user.videoChannels
108 if (Array.isArray(videoChannels) === false) return
109
110 this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name }))
111
112 this.form.patchValue({ channelId: this.userVideoChannels[0].id })
113 }
114 )
103 } 115 }
104 116
105 // The goal is to keep reactive form validation (required field) 117 // The goal is to keep reactive form validation (required field)
diff --git a/client/src/app/videos/+video-edit/video-add.module.ts b/client/src/app/videos/+video-edit/video-add.module.ts
index 141d33ad2..3d937b008 100644
--- a/client/src/app/videos/+video-edit/video-add.module.ts
+++ b/client/src/app/videos/+video-edit/video-add.module.ts
@@ -1,17 +1,14 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2 2
3import { TagInputModule } from 'ngx-chips'
4
5import { VideoAddRoutingModule } from './video-add-routing.module' 3import { VideoAddRoutingModule } from './video-add-routing.module'
6import { VideoAddComponent } from './video-add.component' 4import { VideoAddComponent } from './video-add.component'
7import { VideoService } from '../shared' 5import { VideoEditModule } from './video-edit.module'
8import { SharedModule } from '../../shared' 6import { SharedModule } from '../../shared'
9 7
10@NgModule({ 8@NgModule({
11 imports: [ 9 imports: [
12 TagInputModule,
13
14 VideoAddRoutingModule, 10 VideoAddRoutingModule,
11 VideoEditModule,
15 SharedModule 12 SharedModule
16 ], 13 ],
17 14
@@ -23,8 +20,6 @@ import { SharedModule } from '../../shared'
23 VideoAddComponent 20 VideoAddComponent
24 ], 21 ],
25 22
26 providers: [ 23 providers: [ ]
27 VideoService
28 ]
29}) 24})
30export class VideoAddModule { } 25export class VideoAddModule { }
diff --git a/client/src/app/videos/+video-edit/video-edit.module.ts b/client/src/app/videos/+video-edit/video-edit.module.ts
new file mode 100644
index 000000000..33f654960
--- /dev/null
+++ b/client/src/app/videos/+video-edit/video-edit.module.ts
@@ -0,0 +1,33 @@
1import { NgModule } from '@angular/core'
2
3import { TagInputModule } from 'ngx-chips'
4import { TabsModule } from 'ngx-bootstrap/tabs'
5
6import { VideoService, MarkdownService, VideoDescriptionComponent } from '../shared'
7import { SharedModule } from '../../shared'
8
9@NgModule({
10 imports: [
11 TagInputModule,
12 TabsModule.forRoot(),
13
14 SharedModule
15 ],
16
17 declarations: [
18 VideoDescriptionComponent
19 ],
20
21 exports: [
22 TagInputModule,
23 TabsModule,
24
25 VideoDescriptionComponent
26 ],
27
28 providers: [
29 VideoService,
30 MarkdownService
31 ]
32})
33export class VideoEditModule { }
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html
index 4dcb3ea56..ec040630e 100644
--- a/client/src/app/videos/+video-edit/video-update.component.html
+++ b/client/src/app/videos/+video-edit/video-update.component.html
@@ -62,7 +62,7 @@
62 </div> 62 </div>
63 63
64 <div class="form-group"> 64 <div class="form-group">
65 <label for="tags" class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span> 65 <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
66 <tag-input 66 <tag-input
67 [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" 67 [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
68 formControlName="tags" maxItems="5" modelAsStrings="true" 68 formControlName="tags" maxItems="5" modelAsStrings="true"
@@ -71,11 +71,8 @@
71 71
72 <div class="form-group"> 72 <div class="form-group">
73 <label for="description">Description</label> 73 <label for="description">Description</label>
74 <textarea 74 <my-video-description formControlName="description"></my-video-description>
75 id="description" class="form-control" placeholder="Description..." 75
76 formControlName="description"
77 >
78 </textarea>
79 <div *ngIf="formErrors.description" class="alert alert-danger"> 76 <div *ngIf="formErrors.description" class="alert alert-danger">
80 {{ formErrors.description }} 77 {{ formErrors.description }}
81 </div> 78 </div>
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 30390ac05..6ced77f1a 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -1,6 +1,8 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms' 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { Observable } from 'rxjs/Observable'
5import 'rxjs/add/observable/forkJoin'
4 6
5import { NotificationsService } from 'angular2-notifications' 7import { NotificationsService } from 'angular2-notifications'
6 8
@@ -84,19 +86,26 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
84 this.videoLanguages = this.serverService.getVideoLanguages() 86 this.videoLanguages = this.serverService.getVideoLanguages()
85 87
86 const uuid: string = this.route.snapshot.params['uuid'] 88 const uuid: string = this.route.snapshot.params['uuid']
87 this.videoService.getVideo(uuid)
88 .subscribe(
89 video => {
90 this.video = new VideoEdit(video)
91
92 this.hydrateFormFromVideo()
93 },
94 89
95 err => { 90 this.videoService.getVideo(uuid)
96 console.error(err) 91 .switchMap(video => {
97 this.error = 'Cannot fetch video.' 92 return this.videoService
98 } 93 .loadCompleteDescription(video.descriptionPath)
99 ) 94 .do(description => video.description = description)
95 .map(() => video)
96 })
97 .subscribe(
98 video => {
99 this.video = new VideoEdit(video)
100
101 this.hydrateFormFromVideo()
102 },
103
104 err => {
105 console.error(err)
106 this.error = 'Cannot fetch video.'
107 }
108 )
100 } 109 }
101 110
102 checkForm () { 111 checkForm () {
diff --git a/client/src/app/videos/+video-edit/video-update.module.ts b/client/src/app/videos/+video-edit/video-update.module.ts
index eeb2e35e2..f7bd77c75 100644
--- a/client/src/app/videos/+video-edit/video-update.module.ts
+++ b/client/src/app/videos/+video-edit/video-update.module.ts
@@ -1,17 +1,14 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2 2
3import { TagInputModule } from 'ngx-chips'
4
5import { VideoUpdateRoutingModule } from './video-update-routing.module' 3import { VideoUpdateRoutingModule } from './video-update-routing.module'
6import { VideoUpdateComponent } from './video-update.component' 4import { VideoUpdateComponent } from './video-update.component'
7import { VideoService } from '../shared' 5import { VideoEditModule } from './video-edit.module'
8import { SharedModule } from '../../shared' 6import { SharedModule } from '../../shared'
9 7
10@NgModule({ 8@NgModule({
11 imports: [ 9 imports: [
12 TagInputModule,
13
14 VideoUpdateRoutingModule, 10 VideoUpdateRoutingModule,
11 VideoEditModule,
15 SharedModule 12 SharedModule
16 ], 13 ],
17 14
@@ -23,8 +20,6 @@ import { SharedModule } from '../../shared'
23 VideoUpdateComponent 20 VideoUpdateComponent
24 ], 21 ],
25 22
26 providers: [ 23 providers: [ ]
27 VideoService
28 ]
29}) 24})
30export class VideoUpdateModule { } 25export class VideoUpdateModule { }
diff --git a/client/src/app/videos/+video-watch/video-report.component.html b/client/src/app/videos/+video-watch/video-report.component.html
index 741080ead..ceb7cf50a 100644
--- a/client/src/app/videos/+video-watch/video-report.component.html
+++ b/client/src/app/videos/+video-watch/video-report.component.html
@@ -13,7 +13,7 @@
13 13
14 <form novalidate [formGroup]="form"> 14 <form novalidate [formGroup]="form">
15 <div class="form-group"> 15 <div class="form-group">
16 <label for="description">Reason</label> 16 <label for="reason">Reason</label>
17 <textarea 17 <textarea
18 id="reason" class="form-control" placeholder="Reason..." 18 id="reason" class="form-control" placeholder="Reason..."
19 formControlName="reason" 19 formControlName="reason"
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 4b594e7ed..71f986ccd 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -129,6 +129,16 @@
129 </div> 129 </div>
130 130
131 <div class="video-details-description" [innerHTML]="videoHTMLDescription"></div> 131 <div class="video-details-description" [innerHTML]="videoHTMLDescription"></div>
132
133 <div *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()" class="video-details-description-more">
134 Show more
135 <span class="glyphicon glyphicon-menu-down"></span>
136 </div>
137
138 <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-details-description-more">
139 Show less
140 <span class="glyphicon glyphicon-menu-up"></span>
141 </div>
132 </div> 142 </div>
133 143
134 <div class="video-details-attributes col-xs-4 col-md-3"> 144 <div class="video-details-attributes col-xs-4 col-md-3">
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 01ceab3c5..ab0539fa3 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -170,6 +170,18 @@
170 font-weight: bold; 170 font-weight: bold;
171 margin-bottom: 30px; 171 margin-bottom: 30px;
172 } 172 }
173
174 .video-details-description-more {
175 cursor: pointer;
176 margin-top: 15px;
177 font-weight: bold;
178 color: #acaeb7;
179
180 .glyphicon {
181 position: relative;
182 top: 2px;
183 }
184 }
173 } 185 }
174 186
175 .video-details-attributes { 187 .video-details-attributes {
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 199666bdc..5e2486b9c 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -38,6 +38,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
38 video: VideoDetails = null 38 video: VideoDetails = null
39 videoPlayerLoaded = false 39 videoPlayerLoaded = false
40 videoNotFound = false 40 videoNotFound = false
41
42 completeDescriptionShown = false
43 completeVideoDescription: string
44 shortVideoDescription: string
41 videoHTMLDescription = '' 45 videoHTMLDescription = ''
42 46
43 private paramsSub: Subscription 47 private paramsSub: Subscription
@@ -154,6 +158,36 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
154 ) 158 )
155 } 159 }
156 160
161 showMoreDescription () {
162 this.completeDescriptionShown = true
163
164 if (this.completeVideoDescription === undefined) {
165 return this.loadCompleteDescription()
166 }
167
168 this.updateVideoDescription(this.completeVideoDescription)
169 }
170
171 showLessDescription () {
172 this.completeDescriptionShown = false
173
174 this.updateVideoDescription(this.shortVideoDescription)
175 }
176
177 loadCompleteDescription () {
178 this.videoService.loadCompleteDescription(this.video.descriptionPath)
179 .subscribe(
180 description => {
181 this.shortVideoDescription = this.video.description
182 this.completeVideoDescription = description
183
184 this.updateVideoDescription(this.completeVideoDescription)
185 },
186
187 error => this.notificationsService.error('Error', error.text)
188 )
189 }
190
157 showReportModal (event: Event) { 191 showReportModal (event: Event) {
158 event.preventDefault() 192 event.preventDefault()
159 this.videoReportModal.show() 193 this.videoReportModal.show()
@@ -184,6 +218,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
184 return this.video.isBlackistableBy(this.authService.getUser()) 218 return this.video.isBlackistableBy(this.authService.getUser())
185 } 219 }
186 220
221 private updateVideoDescription (description: string) {
222 this.video.description = description
223 this.setVideoDescriptionHTML()
224 }
225
226 private setVideoDescriptionHTML () {
227 this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
228 }
229
187 private handleError (err: any) { 230 private handleError (err: any) {
188 const errorMessage: string = typeof err === 'string' ? err : err.message 231 const errorMessage: string = typeof err === 'string' ? err : err.message
189 let message = '' 232 let message = ''
@@ -264,7 +307,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
264 }) 307 })
265 }) 308 })
266 309
267 this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description) 310 this.setVideoDescriptionHTML()
268 311
269 this.setOpenGraphTags() 312 this.setOpenGraphTags()
270 this.checkUserRating() 313 this.checkUserRating()
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts
index 09d961dd3..3f1458088 100644
--- a/client/src/app/videos/shared/index.ts
+++ b/client/src/app/videos/shared/index.ts
@@ -4,4 +4,5 @@ export * from './video.model'
4export * from './video-details.model' 4export * from './video-details.model'
5export * from './video-edit.model' 5export * from './video-edit.model'
6export * from './video.service' 6export * from './video.service'
7export * from './video-description.component'
7export * from './video-pagination.model' 8export * from './video-pagination.model'
diff --git a/client/src/app/videos/shared/video-description.component.html b/client/src/app/videos/shared/video-description.component.html
new file mode 100644
index 000000000..7a228857c
--- /dev/null
+++ b/client/src/app/videos/shared/video-description.component.html
@@ -0,0 +1,9 @@
1<textarea
2 [(ngModel)]="description" (ngModelChange)="onModelChange()"
3 id="description" class="form-control" placeholder="My super video">
4</textarea>
5
6<tabset #staticTabs class="previews">
7 <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
8 <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
9</tabset>
diff --git a/client/src/app/videos/shared/video-description.component.scss b/client/src/app/videos/shared/video-description.component.scss
new file mode 100644
index 000000000..d8d73e846
--- /dev/null
+++ b/client/src/app/videos/shared/video-description.component.scss
@@ -0,0 +1,15 @@
1textarea {
2 height: 150px;
3}
4
5.previews /deep/ {
6 .nav {
7 margin-top: 10px;
8 font-size: 0.9em;
9 }
10
11 .tab-content {
12 min-height: 75px;
13 padding: 5px;
14 }
15}
diff --git a/client/src/app/videos/shared/video-description.component.ts b/client/src/app/videos/shared/video-description.component.ts
new file mode 100644
index 000000000..d9ffb7800
--- /dev/null
+++ b/client/src/app/videos/shared/video-description.component.ts
@@ -0,0 +1,68 @@
1import { Component, forwardRef, Input, OnInit } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { Subject } from 'rxjs/Subject'
4import 'rxjs/add/operator/debounceTime'
5import 'rxjs/add/operator/distinctUntilChanged'
6
7import { truncate } from 'lodash'
8
9import { MarkdownService } from './markdown.service'
10
11@Component({
12 selector: 'my-video-description',
13 templateUrl: './video-description.component.html',
14 styleUrls: [ './video-description.component.scss' ],
15 providers: [
16 {
17 provide: NG_VALUE_ACCESSOR,
18 useExisting: forwardRef(() => VideoDescriptionComponent),
19 multi: true
20 }
21 ]
22})
23
24export class VideoDescriptionComponent implements ControlValueAccessor, OnInit {
25 @Input() description = ''
26 truncatedDescriptionHTML = ''
27 descriptionHTML = ''
28
29 private descriptionChanged = new Subject<string>()
30
31 constructor (private markdownService: MarkdownService) {}
32
33 ngOnInit () {
34 this.descriptionChanged
35 .debounceTime(150)
36 .distinctUntilChanged()
37 .subscribe(() => this.updateDescriptionPreviews())
38
39 this.descriptionChanged.next(this.description)
40 }
41
42 propagateChange = (_: any) => { /* empty */ }
43
44 writeValue (description: string) {
45 this.description = description
46
47 this.descriptionChanged.next(this.description)
48 }
49
50 registerOnChange (fn: (_: any) => void) {
51 this.propagateChange = fn
52 }
53
54 registerOnTouched () {
55 // Unused
56 }
57
58 onModelChange () {
59 this.propagateChange(this.description)
60
61 this.descriptionChanged.next(this.description)
62 }
63
64 private updateDescriptionPreviews () {
65 this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
66 this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
67 }
68}
diff --git a/client/src/app/videos/shared/video-details.model.ts b/client/src/app/videos/shared/video-details.model.ts
index 3a6ecc480..68ded5210 100644
--- a/client/src/app/videos/shared/video-details.model.ts
+++ b/client/src/app/videos/shared/video-details.model.ts
@@ -38,12 +38,14 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
38 likes: number 38 likes: number
39 dislikes: number 39 dislikes: number
40 nsfw: boolean 40 nsfw: boolean
41 descriptionPath: string
41 files: VideoFile[] 42 files: VideoFile[]
42 channel: VideoChannel 43 channel: VideoChannel
43 44
44 constructor (hash: VideoDetailsServerModel) { 45 constructor (hash: VideoDetailsServerModel) {
45 super(hash) 46 super(hash)
46 47
48 this.descriptionPath = hash.descriptionPath
47 this.files = hash.files 49 this.files = hash.files
48 this.channel = hash.channel 50 this.channel = hash.channel
49 } 51 }
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
index 8fdc1f213..7d5372334 100644
--- a/client/src/app/videos/shared/video.service.ts
+++ b/client/src/app/videos/shared/video.service.ts
@@ -99,15 +99,11 @@ export class VideoService {
99 .catch((res) => this.restExtractor.handleError(res)) 99 .catch((res) => this.restExtractor.handleError(res))
100 } 100 }
101 101
102 reportVideo (id: number, reason: string) { 102 loadCompleteDescription (descriptionPath: string) {
103 const url = VideoService.BASE_VIDEO_URL + id + '/abuse' 103 return this.authHttp
104 const body: VideoAbuseCreate = { 104 .get(API_URL + descriptionPath)
105 reason 105 .map(res => res['description'])
106 } 106 .catch((res) => this.restExtractor.handleError(res))
107
108 return this.authHttp.post(url, body)
109 .map(this.restExtractor.extractDataBool)
110 .catch(res => this.restExtractor.handleError(res))
111 } 107 }
112 108
113 setVideoLike (id: number) { 109 setVideoLike (id: number) {
diff --git a/client/tslint.json b/client/tslint.json
index 6438519a6..068fe596e 100644
--- a/client/tslint.json
+++ b/client/tslint.json
@@ -21,7 +21,7 @@
21 "no-attribute-parameter-decorator": true, 21 "no-attribute-parameter-decorator": true,
22 "no-input-rename": true, 22 "no-input-rename": true,
23 "no-output-rename": true, 23 "no-output-rename": true,
24 "no-forward-ref": true, 24 "no-forward-ref": false,
25 "use-life-cycle-interface": true, 25 "use-life-cycle-interface": true,
26 "use-pipe-transform-interface": true, 26 "use-pipe-transform-interface": true,
27 "pipe-naming": [true, "camelCase", "my"], 27 "pipe-naming": [true, "camelCase", "my"],