diff options
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' | |||
3 | import { Observable } from 'rxjs/Observable' | 3 | import { Observable } from 'rxjs/Observable' |
4 | import { Subject } from 'rxjs/Subject' | 4 | import { Subject } from 'rxjs/Subject' |
5 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' |
6 | import { ReplaySubject } from 'rxjs/ReplaySubject' | ||
7 | import 'rxjs/add/operator/do' | ||
6 | import 'rxjs/add/operator/map' | 8 | import 'rxjs/add/operator/map' |
7 | import 'rxjs/add/operator/mergeMap' | 9 | import 'rxjs/add/operator/mergeMap' |
8 | import 'rxjs/add/observable/throw' | 10 | import '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 | ||
38 | export const VIDEO_DESCRIPTION = { | 38 | export 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 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | 2 | ||
3 | import { TagInputModule } from 'ngx-chips' | ||
4 | |||
5 | import { VideoAddRoutingModule } from './video-add-routing.module' | 3 | import { VideoAddRoutingModule } from './video-add-routing.module' |
6 | import { VideoAddComponent } from './video-add.component' | 4 | import { VideoAddComponent } from './video-add.component' |
7 | import { VideoService } from '../shared' | 5 | import { VideoEditModule } from './video-edit.module' |
8 | import { SharedModule } from '../../shared' | 6 | import { 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 | }) |
30 | export class VideoAddModule { } | 25 | export 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 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | |||
3 | import { TagInputModule } from 'ngx-chips' | ||
4 | import { TabsModule } from 'ngx-bootstrap/tabs' | ||
5 | |||
6 | import { VideoService, MarkdownService, VideoDescriptionComponent } from '../shared' | ||
7 | import { 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 | }) | ||
33 | export 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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 2 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { Observable } from 'rxjs/Observable' | ||
5 | import 'rxjs/add/observable/forkJoin' | ||
4 | 6 | ||
5 | import { NotificationsService } from 'angular2-notifications' | 7 | import { 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 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | 2 | ||
3 | import { TagInputModule } from 'ngx-chips' | ||
4 | |||
5 | import { VideoUpdateRoutingModule } from './video-update-routing.module' | 3 | import { VideoUpdateRoutingModule } from './video-update-routing.module' |
6 | import { VideoUpdateComponent } from './video-update.component' | 4 | import { VideoUpdateComponent } from './video-update.component' |
7 | import { VideoService } from '../shared' | 5 | import { VideoEditModule } from './video-edit.module' |
8 | import { SharedModule } from '../../shared' | 6 | import { 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 | }) |
30 | export class VideoUpdateModule { } | 25 | export 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' | |||
4 | export * from './video-details.model' | 4 | export * from './video-details.model' |
5 | export * from './video-edit.model' | 5 | export * from './video-edit.model' |
6 | export * from './video.service' | 6 | export * from './video.service' |
7 | export * from './video-description.component' | ||
7 | export * from './video-pagination.model' | 8 | export * 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 @@ | |||
1 | textarea { | ||
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 @@ | |||
1 | import { Component, forwardRef, Input, OnInit } from '@angular/core' | ||
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | ||
3 | import { Subject } from 'rxjs/Subject' | ||
4 | import 'rxjs/add/operator/debounceTime' | ||
5 | import 'rxjs/add/operator/distinctUntilChanged' | ||
6 | |||
7 | import { truncate } from 'lodash' | ||
8 | |||
9 | import { 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 | |||
24 | export 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 }) |