aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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
-rw-r--r--server/models/video/video-interface.ts2
-rw-r--r--server/tests/api/check-params/videos.ts8
23 files changed, 269 insertions, 76 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"],
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index 3a7bc82a4..587652f45 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -138,7 +138,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
138 getOriginalFileHeight: VideoMethods.GetOriginalFileHeight 138 getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
139 getEmbedPath: VideoMethods.GetEmbedPath 139 getEmbedPath: VideoMethods.GetEmbedPath
140 getDescriptionPath: VideoMethods.GetDescriptionPath 140 getDescriptionPath: VideoMethods.GetDescriptionPath
141 getTruncatedDescription : VideoMethods.GetTruncatedDescription 141 getTruncatedDescription: VideoMethods.GetTruncatedDescription
142 142
143 setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> 143 setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
144 addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> 144 addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 765b9c16e..c59f5da93 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -280,9 +280,7 @@ describe('Test videos API validator', function () {
280 licence: 1, 280 licence: 1,
281 language: 6, 281 language: 6,
282 nsfw: false, 282 nsfw: false,
283 description: 'my super description which is very very very very very very very very very very very very very very' + 283 description: 'my super description which is very very very very very very very very very very very very very very long'.repeat(35),
284 'very very very very very very very very very very very very very very very very very very very very very' +
285 'very very very very very very very very very very very very very very very long',
286 tags: [ 'tag1', 'tag2' ], 284 tags: [ 'tag1', 'tag2' ],
287 channelId 285 channelId
288 } 286 }
@@ -617,9 +615,7 @@ describe('Test videos API validator', function () {
617 licence: 2, 615 licence: 2,
618 language: 6, 616 language: 6,
619 nsfw: false, 617 nsfw: false,
620 description: 'my super description which is very very very very very very very very very very very very very very' + 618 description: 'my super description which is very very very very very very very very very very very very very long'.repeat(35),
621 'very very very very very very very very very very very very very very very very very very very very very' +
622 'very very very very very very very very very very very very very very very long',
623 tags: [ 'tag1', 'tag2' ] 619 tags: [ 'tag1', 'tag2' ]
624 } 620 }
625 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 621 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })