diff options
Diffstat (limited to 'client/src/app')
123 files changed, 1627 insertions, 1508 deletions
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts index 215e281bb..11e442f6b 100644 --- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts | |||
@@ -1,6 +1,12 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { Notifier, ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { FormReactive, FormValidatorService, InstanceValidatorsService } from '@app/shared/shared-forms' | 3 | import { |
4 | BODY_VALIDATOR, | ||
5 | FROM_EMAIL_VALIDATOR, | ||
6 | FROM_NAME_VALIDATOR, | ||
7 | SUBJECT_VALIDATOR | ||
8 | } from '@app/shared/form-validators/instance-validators' | ||
9 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
4 | import { InstanceService } from '@app/shared/shared-instance' | 10 | import { InstanceService } from '@app/shared/shared-instance' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 11 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 12 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
@@ -22,7 +28,6 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit { | |||
22 | constructor ( | 28 | constructor ( |
23 | protected formValidatorService: FormValidatorService, | 29 | protected formValidatorService: FormValidatorService, |
24 | private modalService: NgbModal, | 30 | private modalService: NgbModal, |
25 | private instanceValidatorsService: InstanceValidatorsService, | ||
26 | private instanceService: InstanceService, | 31 | private instanceService: InstanceService, |
27 | private serverService: ServerService, | 32 | private serverService: ServerService, |
28 | private notifier: Notifier | 33 | private notifier: Notifier |
@@ -40,10 +45,10 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit { | |||
40 | .subscribe(config => this.serverConfig = config) | 45 | .subscribe(config => this.serverConfig = config) |
41 | 46 | ||
42 | this.buildForm({ | 47 | this.buildForm({ |
43 | fromName: this.instanceValidatorsService.FROM_NAME, | 48 | fromName: FROM_NAME_VALIDATOR, |
44 | fromEmail: this.instanceValidatorsService.FROM_EMAIL, | 49 | fromEmail: FROM_EMAIL_VALIDATOR, |
45 | subject: this.instanceValidatorsService.SUBJECT, | 50 | subject: SUBJECT_VALIDATOR, |
46 | body: this.instanceValidatorsService.BODY | 51 | body: BODY_VALIDATOR |
47 | }) | 52 | }) |
48 | } | 53 | } |
49 | 54 | ||
diff --git a/client/src/app/+about/about-routing.module.ts b/client/src/app/+about/about-routing.module.ts index 91ccb846f..828b2884c 100644 --- a/client/src/app/+about/about-routing.module.ts +++ b/client/src/app/+about/about-routing.module.ts | |||
@@ -23,7 +23,7 @@ const aboutRoutes: Routes = [ | |||
23 | component: AboutInstanceComponent, | 23 | component: AboutInstanceComponent, |
24 | data: { | 24 | data: { |
25 | meta: { | 25 | meta: { |
26 | title: 'About this instance' | 26 | title: $localize`About this instance` |
27 | } | 27 | } |
28 | }, | 28 | }, |
29 | resolve: { | 29 | resolve: { |
@@ -35,7 +35,7 @@ const aboutRoutes: Routes = [ | |||
35 | component: AboutPeertubeComponent, | 35 | component: AboutPeertubeComponent, |
36 | data: { | 36 | data: { |
37 | meta: { | 37 | meta: { |
38 | title: 'About PeerTube' | 38 | title: $localize`About PeerTube` |
39 | } | 39 | } |
40 | } | 40 | } |
41 | }, | 41 | }, |
@@ -44,7 +44,7 @@ const aboutRoutes: Routes = [ | |||
44 | component: AboutFollowsComponent, | 44 | component: AboutFollowsComponent, |
45 | data: { | 45 | data: { |
46 | meta: { | 46 | meta: { |
47 | title: 'About follows' | 47 | title: $localize`About follows` |
48 | } | 48 | } |
49 | } | 49 | } |
50 | } | 50 | } |
diff --git a/client/src/app/+accounts/accounts-routing.module.ts b/client/src/app/+accounts/accounts-routing.module.ts index 45b24eb55..d2ca784b0 100644 --- a/client/src/app/+accounts/accounts-routing.module.ts +++ b/client/src/app/+accounts/accounts-routing.module.ts | |||
@@ -26,7 +26,7 @@ const accountsRoutes: Routes = [ | |||
26 | component: AccountVideosComponent, | 26 | component: AccountVideosComponent, |
27 | data: { | 27 | data: { |
28 | meta: { | 28 | meta: { |
29 | title: 'Account videos' | 29 | title: $localize`Account videos` |
30 | }, | 30 | }, |
31 | reuse: { | 31 | reuse: { |
32 | enabled: true, | 32 | enabled: true, |
@@ -39,7 +39,7 @@ const accountsRoutes: Routes = [ | |||
39 | component: AccountVideoChannelsComponent, | 39 | component: AccountVideoChannelsComponent, |
40 | data: { | 40 | data: { |
41 | meta: { | 41 | meta: { |
42 | title: 'Account video channels' | 42 | title: $localize`Account video channels` |
43 | } | 43 | } |
44 | } | 44 | } |
45 | }, | 45 | }, |
@@ -48,7 +48,7 @@ const accountsRoutes: Routes = [ | |||
48 | component: AccountAboutComponent, | 48 | component: AccountAboutComponent, |
49 | data: { | 49 | data: { |
50 | meta: { | 50 | meta: { |
51 | title: 'About account' | 51 | title: $localize`About account` |
52 | } | 52 | } |
53 | } | 53 | } |
54 | } | 54 | } |
diff --git a/client/src/app/+admin/config/config.routes.ts b/client/src/app/+admin/config/config.routes.ts index 7c1a1a166..1b76b29cc 100644 --- a/client/src/app/+admin/config/config.routes.ts +++ b/client/src/app/+admin/config/config.routes.ts | |||
@@ -23,7 +23,7 @@ export const ConfigRoutes: Routes = [ | |||
23 | component: EditCustomConfigComponent, | 23 | component: EditCustomConfigComponent, |
24 | data: { | 24 | data: { |
25 | meta: { | 25 | meta: { |
26 | title: 'Edit custom configuration' | 26 | title: $localize`Edit custom configuration` |
27 | } | 27 | } |
28 | } | 28 | } |
29 | } | 29 | } |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 3a60b144f..78e9dd5e5 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -5,12 +5,19 @@ import { ConfigService } from '@app/+admin/config/shared/config.service' | |||
5 | import { Notifier } from '@app/core' | 5 | import { Notifier } from '@app/core' |
6 | import { ServerService } from '@app/core/server/server.service' | 6 | import { ServerService } from '@app/core/server/server.service' |
7 | import { | 7 | import { |
8 | CustomConfigValidatorsService, | 8 | ADMIN_EMAIL_VALIDATOR, |
9 | FormReactive, | 9 | CACHE_CAPTIONS_SIZE_VALIDATOR, |
10 | FormValidatorService, | 10 | CACHE_PREVIEWS_SIZE_VALIDATOR, |
11 | SelectOptionsItem, | 11 | INDEX_URL_VALIDATOR, |
12 | UserValidatorsService | 12 | INSTANCE_NAME_VALIDATOR, |
13 | } from '@app/shared/shared-forms' | 13 | INSTANCE_SHORT_DESCRIPTION_VALIDATOR, |
14 | SEARCH_INDEX_URL_VALIDATOR, | ||
15 | SERVICES_TWITTER_USERNAME_VALIDATOR, | ||
16 | SIGNUP_LIMIT_VALIDATOR, | ||
17 | TRANSCODING_THREADS_VALIDATOR | ||
18 | } from '@app/shared/form-validators/custom-config-validators' | ||
19 | import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators' | ||
20 | import { FormReactive, FormValidatorService, SelectOptionsItem } from '@app/shared/shared-forms' | ||
14 | import { NgbNav } from '@ng-bootstrap/ng-bootstrap' | 21 | import { NgbNav } from '@ng-bootstrap/ng-bootstrap' |
15 | import { CustomConfig, ServerConfig } from '@shared/models' | 22 | import { CustomConfig, ServerConfig } from '@shared/models' |
16 | 23 | ||
@@ -37,8 +44,6 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
37 | constructor ( | 44 | constructor ( |
38 | private viewportScroller: ViewportScroller, | 45 | private viewportScroller: ViewportScroller, |
39 | protected formValidatorService: FormValidatorService, | 46 | protected formValidatorService: FormValidatorService, |
40 | private customConfigValidatorsService: CustomConfigValidatorsService, | ||
41 | private userValidatorsService: UserValidatorsService, | ||
42 | private notifier: Notifier, | 47 | private notifier: Notifier, |
43 | private configService: ConfigService, | 48 | private configService: ConfigService, |
44 | private serverService: ServerService | 49 | private serverService: ServerService |
@@ -110,8 +115,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
110 | 115 | ||
111 | const formGroupData: { [key in keyof CustomConfig ]: any } = { | 116 | const formGroupData: { [key in keyof CustomConfig ]: any } = { |
112 | instance: { | 117 | instance: { |
113 | name: this.customConfigValidatorsService.INSTANCE_NAME, | 118 | name: INSTANCE_NAME_VALIDATOR, |
114 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 119 | shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR, |
115 | description: null, | 120 | description: null, |
116 | 121 | ||
117 | isNSFW: false, | 122 | isNSFW: false, |
@@ -143,21 +148,21 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
143 | }, | 148 | }, |
144 | services: { | 149 | services: { |
145 | twitter: { | 150 | twitter: { |
146 | username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, | 151 | username: SERVICES_TWITTER_USERNAME_VALIDATOR, |
147 | whitelisted: null | 152 | whitelisted: null |
148 | } | 153 | } |
149 | }, | 154 | }, |
150 | cache: { | 155 | cache: { |
151 | previews: { | 156 | previews: { |
152 | size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE | 157 | size: CACHE_PREVIEWS_SIZE_VALIDATOR |
153 | }, | 158 | }, |
154 | captions: { | 159 | captions: { |
155 | size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE | 160 | size: CACHE_CAPTIONS_SIZE_VALIDATOR |
156 | } | 161 | } |
157 | }, | 162 | }, |
158 | signup: { | 163 | signup: { |
159 | enabled: null, | 164 | enabled: null, |
160 | limit: this.customConfigValidatorsService.SIGNUP_LIMIT, | 165 | limit: SIGNUP_LIMIT_VALIDATOR, |
161 | requiresEmailVerification: null | 166 | requiresEmailVerification: null |
162 | }, | 167 | }, |
163 | import: { | 168 | import: { |
@@ -171,18 +176,18 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
171 | } | 176 | } |
172 | }, | 177 | }, |
173 | admin: { | 178 | admin: { |
174 | email: this.customConfigValidatorsService.ADMIN_EMAIL | 179 | email: ADMIN_EMAIL_VALIDATOR |
175 | }, | 180 | }, |
176 | contactForm: { | 181 | contactForm: { |
177 | enabled: null | 182 | enabled: null |
178 | }, | 183 | }, |
179 | user: { | 184 | user: { |
180 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 185 | videoQuota: USER_VIDEO_QUOTA_VALIDATOR, |
181 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY | 186 | videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR |
182 | }, | 187 | }, |
183 | transcoding: { | 188 | transcoding: { |
184 | enabled: null, | 189 | enabled: null, |
185 | threads: this.customConfigValidatorsService.TRANSCODING_THREADS, | 190 | threads: TRANSCODING_THREADS_VALIDATOR, |
186 | allowAdditionalExtensions: null, | 191 | allowAdditionalExtensions: null, |
187 | allowAudioFiles: null, | 192 | allowAudioFiles: null, |
188 | resolutions: {}, | 193 | resolutions: {}, |
@@ -213,7 +218,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
213 | }, | 218 | }, |
214 | autoFollowIndex: { | 219 | autoFollowIndex: { |
215 | enabled: null, | 220 | enabled: null, |
216 | indexUrl: this.customConfigValidatorsService.INDEX_URL | 221 | indexUrl: INDEX_URL_VALIDATOR |
217 | } | 222 | } |
218 | } | 223 | } |
219 | }, | 224 | }, |
@@ -230,7 +235,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
230 | }, | 235 | }, |
231 | searchIndex: { | 236 | searchIndex: { |
232 | enabled: null, | 237 | enabled: null, |
233 | url: this.customConfigValidatorsService.SEARCH_INDEX_URL, | 238 | url: SEARCH_INDEX_URL_VALIDATOR, |
234 | disableLocalSearch: null, | 239 | disableLocalSearch: null, |
235 | isDefaultSearch: null | 240 | isDefaultSearch: null |
236 | } | 241 | } |
diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts index 817074536..cd70daf77 100644 --- a/client/src/app/+admin/follows/follows.routes.ts +++ b/client/src/app/+admin/follows/follows.routes.ts | |||
@@ -25,7 +25,7 @@ export const FollowsRoutes: Routes = [ | |||
25 | component: FollowingListComponent, | 25 | component: FollowingListComponent, |
26 | data: { | 26 | data: { |
27 | meta: { | 27 | meta: { |
28 | title: 'Following list' | 28 | title: $localize`Following list` |
29 | } | 29 | } |
30 | } | 30 | } |
31 | }, | 31 | }, |
@@ -34,7 +34,7 @@ export const FollowsRoutes: Routes = [ | |||
34 | component: FollowersListComponent, | 34 | component: FollowersListComponent, |
35 | data: { | 35 | data: { |
36 | meta: { | 36 | meta: { |
37 | title: 'Followers list' | 37 | title: $localize`Followers list` |
38 | } | 38 | } |
39 | } | 39 | } |
40 | }, | 40 | }, |
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index 8a31a54dc..b60dd5334 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts | |||
@@ -33,7 +33,7 @@ export const ModerationRoutes: Routes = [ | |||
33 | data: { | 33 | data: { |
34 | userRight: UserRight.MANAGE_ABUSES, | 34 | userRight: UserRight.MANAGE_ABUSES, |
35 | meta: { | 35 | meta: { |
36 | title: 'Reports' | 36 | title: $localize`Reports` |
37 | } | 37 | } |
38 | } | 38 | } |
39 | }, | 39 | }, |
@@ -64,7 +64,7 @@ export const ModerationRoutes: Routes = [ | |||
64 | data: { | 64 | data: { |
65 | userRight: UserRight.MANAGE_VIDEO_BLACKLIST, | 65 | userRight: UserRight.MANAGE_VIDEO_BLACKLIST, |
66 | meta: { | 66 | meta: { |
67 | title: 'Videos blocked' | 67 | title: $localize`Videos blocked` |
68 | } | 68 | } |
69 | } | 69 | } |
70 | }, | 70 | }, |
@@ -75,7 +75,7 @@ export const ModerationRoutes: Routes = [ | |||
75 | data: { | 75 | data: { |
76 | userRight: UserRight.MANAGE_ACCOUNTS_BLOCKLIST, | 76 | userRight: UserRight.MANAGE_ACCOUNTS_BLOCKLIST, |
77 | meta: { | 77 | meta: { |
78 | title: 'Muted accounts' | 78 | title: $localize`Muted accounts` |
79 | } | 79 | } |
80 | } | 80 | } |
81 | }, | 81 | }, |
@@ -86,7 +86,7 @@ export const ModerationRoutes: Routes = [ | |||
86 | data: { | 86 | data: { |
87 | userRight: UserRight.MANAGE_SERVERS_BLOCKLIST, | 87 | userRight: UserRight.MANAGE_SERVERS_BLOCKLIST, |
88 | meta: { | 88 | meta: { |
89 | title: 'Muted instances' | 89 | title: $localize`Muted instances` |
90 | } | 90 | } |
91 | } | 91 | } |
92 | } | 92 | } |
diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.html b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.html index f3fc429ff..cb2894568 100644 --- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.html +++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.html | |||
@@ -7,38 +7,7 @@ | |||
7 | 7 | ||
8 | <form *ngIf="hasRegisteredSettings()" role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | 8 | <form *ngIf="hasRegisteredSettings()" role="form" (ngSubmit)="formValidated()" [formGroup]="form"> |
9 | <div class="form-group" *ngFor="let setting of registeredSettings"> | 9 | <div class="form-group" *ngFor="let setting of registeredSettings"> |
10 | <label *ngIf="setting.type !== 'input-checkbox'" [attr.for]="setting.name" [innerHTML]="setting.label"></label> | 10 | <my-dynamic-form-field [form]="form" [setting]="setting" [formErrors]="formErrors"></my-dynamic-form-field> |
11 | |||
12 | <input *ngIf="setting.type === 'input'" type="text" [id]="setting.name" [formControlName]="setting.name" /> | ||
13 | |||
14 | <textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea> | ||
15 | |||
16 | <my-help *ngIf="setting.type === 'markdown-text'" helpType="markdownText"></my-help> | ||
17 | |||
18 | <my-help *ngIf="setting.type === 'markdown-enhanced'" helpType="markdownEnhanced"></my-help> | ||
19 | |||
20 | <my-markdown-textarea | ||
21 | *ngIf="setting.type === 'markdown-text'" | ||
22 | markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | ||
23 | [classes]="{ 'input-error': formErrors['settings.name'] }" | ||
24 | ></my-markdown-textarea> | ||
25 | |||
26 | <my-markdown-textarea | ||
27 | *ngIf="setting.type === 'markdown-enhanced'" | ||
28 | markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | ||
29 | [classes]="{ 'input-error': formErrors['settings.name'] }" | ||
30 | ></my-markdown-textarea> | ||
31 | |||
32 | <my-peertube-checkbox | ||
33 | *ngIf="setting.type === 'input-checkbox'" | ||
34 | [id]="setting.name" | ||
35 | [formControlName]="setting.name" | ||
36 | [labelInnerHTML]="setting.label" | ||
37 | ></my-peertube-checkbox> | ||
38 | |||
39 | <div *ngIf="formErrors[setting.name]" class="form-error"> | ||
40 | {{ formErrors[setting.name] }} | ||
41 | </div> | ||
42 | </div> | 11 | </div> |
43 | 12 | ||
44 | <input type="submit" i18n value="Update plugin settings" [disabled]="!form.valid"> | 13 | <input type="submit" i18n value="Update plugin settings" [disabled]="!form.valid"> |
diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss index cc35aec57..5ab6e5f1b 100644 --- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss +++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss | |||
@@ -5,22 +5,6 @@ h2 { | |||
5 | margin-bottom: 20px; | 5 | margin-bottom: 20px; |
6 | } | 6 | } |
7 | 7 | ||
8 | input:not([type=submit]) { | ||
9 | @include peertube-input-text(340px); | ||
10 | |||
11 | display: block; | ||
12 | } | ||
13 | |||
14 | textarea { | ||
15 | @include peertube-textarea(340px, 200px); | ||
16 | |||
17 | display: block; | ||
18 | } | ||
19 | |||
20 | .peertube-select-container { | ||
21 | @include peertube-select-container(340px); | ||
22 | } | ||
23 | |||
24 | input[type=submit], button { | 8 | input[type=submit], button { |
25 | @include peertube-button; | 9 | @include peertube-button; |
26 | @include orange-button; | 10 | @include orange-button; |
diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts index a33f01691..1acaf9674 100644 --- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts +++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts | |||
@@ -3,7 +3,8 @@ import { map, switchMap } from 'rxjs/operators' | |||
3 | import { Component, OnDestroy, OnInit } from '@angular/core' | 3 | import { Component, OnDestroy, OnInit } from '@angular/core' |
4 | import { ActivatedRoute } from '@angular/router' | 4 | import { ActivatedRoute } from '@angular/router' |
5 | import { Notifier } from '@app/core' | 5 | import { Notifier } from '@app/core' |
6 | import { BuildFormArgument, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 6 | import { BuildFormArgument } from '@app/shared/form-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
7 | import { PeerTubePlugin, RegisterServerSettingOptions } from '@shared/models' | 8 | import { PeerTubePlugin, RegisterServerSettingOptions } from '@shared/models' |
8 | import { PluginApiService } from '../shared/plugin-api.service' | 9 | import { PluginApiService } from '../shared/plugin-api.service' |
9 | 10 | ||
diff --git a/client/src/app/+admin/plugins/plugins.routes.ts b/client/src/app/+admin/plugins/plugins.routes.ts index 4bef27be3..4cf55dda2 100644 --- a/client/src/app/+admin/plugins/plugins.routes.ts +++ b/client/src/app/+admin/plugins/plugins.routes.ts | |||
@@ -25,7 +25,7 @@ export const PluginsRoutes: Routes = [ | |||
25 | component: PluginListInstalledComponent, | 25 | component: PluginListInstalledComponent, |
26 | data: { | 26 | data: { |
27 | meta: { | 27 | meta: { |
28 | title: 'List installed plugins' | 28 | title: $localize`List installed plugins` |
29 | } | 29 | } |
30 | } | 30 | } |
31 | }, | 31 | }, |
@@ -34,7 +34,7 @@ export const PluginsRoutes: Routes = [ | |||
34 | component: PluginSearchComponent, | 34 | component: PluginSearchComponent, |
35 | data: { | 35 | data: { |
36 | meta: { | 36 | meta: { |
37 | title: 'Search plugins' | 37 | title: $localize`Search plugins` |
38 | } | 38 | } |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -43,7 +43,7 @@ export const PluginsRoutes: Routes = [ | |||
43 | component: PluginShowInstalledComponent, | 43 | component: PluginShowInstalledComponent, |
44 | data: { | 44 | data: { |
45 | meta: { | 45 | meta: { |
46 | title: 'Show plugin' | 46 | title: $localize`Show plugin` |
47 | } | 47 | } |
48 | } | 48 | } |
49 | } | 49 | } |
diff --git a/client/src/app/+admin/system/system.routes.ts b/client/src/app/+admin/system/system.routes.ts index 0e8d98519..72ab6705a 100644 --- a/client/src/app/+admin/system/system.routes.ts +++ b/client/src/app/+admin/system/system.routes.ts | |||
@@ -10,8 +10,6 @@ export const SystemRoutes: Routes = [ | |||
10 | { | 10 | { |
11 | path: 'system', | 11 | path: 'system', |
12 | component: SystemComponent, | 12 | component: SystemComponent, |
13 | data: { | ||
14 | }, | ||
15 | children: [ | 13 | children: [ |
16 | { | 14 | { |
17 | path: '', | 15 | path: '', |
@@ -25,7 +23,7 @@ export const SystemRoutes: Routes = [ | |||
25 | data: { | 23 | data: { |
26 | meta: { | 24 | meta: { |
27 | userRight: UserRight.MANAGE_JOBS, | 25 | userRight: UserRight.MANAGE_JOBS, |
28 | title: 'Jobs' | 26 | title: $localize`Jobs` |
29 | } | 27 | } |
30 | } | 28 | } |
31 | }, | 29 | }, |
@@ -36,7 +34,7 @@ export const SystemRoutes: Routes = [ | |||
36 | data: { | 34 | data: { |
37 | meta: { | 35 | meta: { |
38 | userRight: UserRight.MANAGE_LOGS, | 36 | userRight: UserRight.MANAGE_LOGS, |
39 | title: 'Logs' | 37 | title: $localize`Logs` |
40 | } | 38 | } |
41 | } | 39 | } |
42 | }, | 40 | }, |
@@ -47,7 +45,7 @@ export const SystemRoutes: Routes = [ | |||
47 | data: { | 45 | data: { |
48 | meta: { | 46 | meta: { |
49 | userRight: UserRight.MANAGE_DEBUG, | 47 | userRight: UserRight.MANAGE_DEBUG, |
50 | title: 'Debug' | 48 | title: $localize`Debug` |
51 | } | 49 | } |
52 | } | 50 | } |
53 | } | 51 | } |
diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts index 36d71a927..d0aac1cb9 100644 --- a/client/src/app/+admin/users/user-edit/user-create.component.ts +++ b/client/src/app/+admin/users/user-edit/user-create.component.ts | |||
@@ -2,7 +2,17 @@ import { Component, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { ConfigService } from '@app/+admin/config/shared/config.service' | 3 | import { ConfigService } from '@app/+admin/config/shared/config.service' |
4 | import { AuthService, Notifier, ScreenService, ServerService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, ScreenService, ServerService, UserService } from '@app/core' |
5 | import { FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 5 | import { |
6 | USER_CHANNEL_NAME_VALIDATOR, | ||
7 | USER_EMAIL_VALIDATOR, | ||
8 | USER_PASSWORD_OPTIONAL_VALIDATOR, | ||
9 | USER_PASSWORD_VALIDATOR, | ||
10 | USER_ROLE_VALIDATOR, | ||
11 | USER_USERNAME_VALIDATOR, | ||
12 | USER_VIDEO_QUOTA_DAILY_VALIDATOR, | ||
13 | USER_VIDEO_QUOTA_VALIDATOR | ||
14 | } from '@app/shared/form-validators/user-validators' | ||
15 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
6 | import { UserCreate, UserRole } from '@shared/models' | 16 | import { UserCreate, UserRole } from '@shared/models' |
7 | import { UserEdit } from './user-edit' | 17 | import { UserEdit } from './user-edit' |
8 | 18 | ||
@@ -20,7 +30,6 @@ export class UserCreateComponent extends UserEdit implements OnInit { | |||
20 | protected configService: ConfigService, | 30 | protected configService: ConfigService, |
21 | protected screenService: ScreenService, | 31 | protected screenService: ScreenService, |
22 | protected auth: AuthService, | 32 | protected auth: AuthService, |
23 | private userValidatorsService: UserValidatorsService, | ||
24 | private route: ActivatedRoute, | 33 | private route: ActivatedRoute, |
25 | private router: Router, | 34 | private router: Router, |
26 | private notifier: Notifier, | 35 | private notifier: Notifier, |
@@ -41,13 +50,13 @@ export class UserCreateComponent extends UserEdit implements OnInit { | |||
41 | } | 50 | } |
42 | 51 | ||
43 | this.buildForm({ | 52 | this.buildForm({ |
44 | username: this.userValidatorsService.USER_USERNAME, | 53 | username: USER_USERNAME_VALIDATOR, |
45 | channelName: this.userValidatorsService.USER_CHANNEL_NAME, | 54 | channelName: USER_CHANNEL_NAME_VALIDATOR, |
46 | email: this.userValidatorsService.USER_EMAIL, | 55 | email: USER_EMAIL_VALIDATOR, |
47 | password: this.isPasswordOptional() ? this.userValidatorsService.USER_PASSWORD_OPTIONAL : this.userValidatorsService.USER_PASSWORD, | 56 | password: this.isPasswordOptional() ? USER_PASSWORD_OPTIONAL_VALIDATOR : USER_PASSWORD_VALIDATOR, |
48 | role: this.userValidatorsService.USER_ROLE, | 57 | role: USER_ROLE_VALIDATOR, |
49 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 58 | videoQuota: USER_VIDEO_QUOTA_VALIDATOR, |
50 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | 59 | videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR, |
51 | byPassAutoBlock: null | 60 | byPassAutoBlock: null |
52 | }, defaultValues) | 61 | }, defaultValues) |
53 | } | 62 | } |
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.ts b/client/src/app/+admin/users/user-edit/user-password.component.ts index 25f13495a..05d52b17f 100644 --- a/client/src/app/+admin/users/user-edit/user-password.component.ts +++ b/client/src/app/+admin/users/user-edit/user-password.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { Notifier, UserService } from '@app/core' | 2 | import { Notifier, UserService } from '@app/core' |
3 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 3 | import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
4 | import { UserUpdate } from '@shared/models' | 5 | import { UserUpdate } from '@shared/models' |
5 | 6 | ||
6 | @Component({ | 7 | @Component({ |
@@ -17,7 +18,6 @@ export class UserPasswordComponent extends FormReactive implements OnInit { | |||
17 | 18 | ||
18 | constructor ( | 19 | constructor ( |
19 | protected formValidatorService: FormValidatorService, | 20 | protected formValidatorService: FormValidatorService, |
20 | private userValidatorsService: UserValidatorsService, | ||
21 | private notifier: Notifier, | 21 | private notifier: Notifier, |
22 | private userService: UserService | 22 | private userService: UserService |
23 | ) { | 23 | ) { |
@@ -26,7 +26,7 @@ export class UserPasswordComponent extends FormReactive implements OnInit { | |||
26 | 26 | ||
27 | ngOnInit () { | 27 | ngOnInit () { |
28 | this.buildForm({ | 28 | this.buildForm({ |
29 | password: this.userValidatorsService.USER_PASSWORD | 29 | password: USER_PASSWORD_VALIDATOR |
30 | }) | 30 | }) |
31 | } | 31 | } |
32 | 32 | ||
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts index 55bc7290e..e16f66a2b 100644 --- a/client/src/app/+admin/users/user-edit/user-update.component.ts +++ b/client/src/app/+admin/users/user-edit/user-update.component.ts | |||
@@ -3,7 +3,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { ConfigService } from '@app/+admin/config/shared/config.service' | 4 | import { ConfigService } from '@app/+admin/config/shared/config.service' |
5 | import { AuthService, Notifier, ScreenService, ServerService, User, UserService } from '@app/core' | 5 | import { AuthService, Notifier, ScreenService, ServerService, User, UserService } from '@app/core' |
6 | import { FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 6 | import { |
7 | USER_EMAIL_VALIDATOR, | ||
8 | USER_ROLE_VALIDATOR, | ||
9 | USER_VIDEO_QUOTA_DAILY_VALIDATOR, | ||
10 | USER_VIDEO_QUOTA_VALIDATOR | ||
11 | } from '@app/shared/form-validators/user-validators' | ||
12 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
7 | import { User as UserType, UserAdminFlag, UserRole, UserUpdate } from '@shared/models' | 13 | import { User as UserType, UserAdminFlag, UserRole, UserUpdate } from '@shared/models' |
8 | import { UserEdit } from './user-edit' | 14 | import { UserEdit } from './user-edit' |
9 | 15 | ||
@@ -23,7 +29,6 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
23 | protected configService: ConfigService, | 29 | protected configService: ConfigService, |
24 | protected screenService: ScreenService, | 30 | protected screenService: ScreenService, |
25 | protected auth: AuthService, | 31 | protected auth: AuthService, |
26 | private userValidatorsService: UserValidatorsService, | ||
27 | private route: ActivatedRoute, | 32 | private route: ActivatedRoute, |
28 | private router: Router, | 33 | private router: Router, |
29 | private notifier: Notifier, | 34 | private notifier: Notifier, |
@@ -44,10 +49,10 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
44 | } | 49 | } |
45 | 50 | ||
46 | this.buildForm({ | 51 | this.buildForm({ |
47 | email: this.userValidatorsService.USER_EMAIL, | 52 | email: USER_EMAIL_VALIDATOR, |
48 | role: this.userValidatorsService.USER_ROLE, | 53 | role: USER_ROLE_VALIDATOR, |
49 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 54 | videoQuota: USER_VIDEO_QUOTA_VALIDATOR, |
50 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | 55 | videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR, |
51 | byPassAutoBlock: null | 56 | byPassAutoBlock: null |
52 | }, defaultValues) | 57 | }, defaultValues) |
53 | 58 | ||
diff --git a/client/src/app/+admin/users/users.routes.ts b/client/src/app/+admin/users/users.routes.ts index 6560f0260..5183498d6 100644 --- a/client/src/app/+admin/users/users.routes.ts +++ b/client/src/app/+admin/users/users.routes.ts | |||
@@ -24,7 +24,7 @@ export const UsersRoutes: Routes = [ | |||
24 | component: UserListComponent, | 24 | component: UserListComponent, |
25 | data: { | 25 | data: { |
26 | meta: { | 26 | meta: { |
27 | title: 'Users list' | 27 | title: $localize`Users list` |
28 | } | 28 | } |
29 | } | 29 | } |
30 | }, | 30 | }, |
@@ -33,7 +33,7 @@ export const UsersRoutes: Routes = [ | |||
33 | component: UserCreateComponent, | 33 | component: UserCreateComponent, |
34 | data: { | 34 | data: { |
35 | meta: { | 35 | meta: { |
36 | title: 'Create a user' | 36 | title: $localize`Create a user` |
37 | } | 37 | } |
38 | }, | 38 | }, |
39 | resolve: { | 39 | resolve: { |
@@ -45,7 +45,7 @@ export const UsersRoutes: Routes = [ | |||
45 | component: UserUpdateComponent, | 45 | component: UserUpdateComponent, |
46 | data: { | 46 | data: { |
47 | meta: { | 47 | meta: { |
48 | title: 'Update a user' | 48 | title: $localize`Update a user` |
49 | } | 49 | } |
50 | } | 50 | } |
51 | } | 51 | } |
diff --git a/client/src/app/+login/login-routing.module.ts b/client/src/app/+login/login-routing.module.ts index aad55eac8..258ddc5c1 100644 --- a/client/src/app/+login/login-routing.module.ts +++ b/client/src/app/+login/login-routing.module.ts | |||
@@ -11,7 +11,7 @@ const loginRoutes: Routes = [ | |||
11 | canActivate: [ MetaGuard ], | 11 | canActivate: [ MetaGuard ], |
12 | data: { | 12 | data: { |
13 | meta: { | 13 | meta: { |
14 | title: 'Login' | 14 | title: $localize`Login` |
15 | } | 15 | } |
16 | }, | 16 | }, |
17 | resolve: { | 17 | resolve: { |
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index e9336172e..351750453 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts | |||
@@ -3,7 +3,8 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angula | |||
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { FormReactive, FormValidatorService, LoginValidatorsService } from '@app/shared/shared-forms' | 6 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
7 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 8 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
8 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | 9 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' |
9 | 10 | ||
@@ -31,7 +32,6 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
31 | protected formValidatorService: FormValidatorService, | 32 | protected formValidatorService: FormValidatorService, |
32 | private route: ActivatedRoute, | 33 | private route: ActivatedRoute, |
33 | private modalService: NgbModal, | 34 | private modalService: NgbModal, |
34 | private loginValidatorsService: LoginValidatorsService, | ||
35 | private authService: AuthService, | 35 | private authService: AuthService, |
36 | private userService: UserService, | 36 | private userService: UserService, |
37 | private redirectService: RedirectService, | 37 | private redirectService: RedirectService, |
@@ -65,8 +65,8 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
65 | } | 65 | } |
66 | 66 | ||
67 | this.buildForm({ | 67 | this.buildForm({ |
68 | username: this.loginValidatorsService.LOGIN_USERNAME, | 68 | username: LOGIN_USERNAME_VALIDATOR, |
69 | password: this.loginValidatorsService.LOGIN_PASSWORD | 69 | password: LOGIN_PASSWORD_VALIDATOR |
70 | }) | 70 | }) |
71 | } | 71 | } |
72 | 72 | ||
diff --git a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-create.component.ts b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-create.component.ts index 5c438c3bf..e2ea87fb8 100644 --- a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-create.component.ts +++ b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-create.component.ts | |||
@@ -1,7 +1,13 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { AuthService, Notifier } from '@app/core' | 3 | import { AuthService, Notifier } from '@app/core' |
4 | import { FormValidatorService, VideoChannelValidatorsService } from '@app/shared/shared-forms' | 4 | import { |
5 | VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, | ||
6 | VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, | ||
7 | VIDEO_CHANNEL_NAME_VALIDATOR, | ||
8 | VIDEO_CHANNEL_SUPPORT_VALIDATOR | ||
9 | } from '@app/shared/form-validators/video-channel-validators' | ||
10 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { VideoChannelService } from '@app/shared/shared-main' | 11 | import { VideoChannelService } from '@app/shared/shared-main' |
6 | import { VideoChannelCreate } from '@shared/models' | 12 | import { VideoChannelCreate } from '@shared/models' |
7 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' | 13 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' |
@@ -17,7 +23,6 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE | |||
17 | constructor ( | 23 | constructor ( |
18 | protected formValidatorService: FormValidatorService, | 24 | protected formValidatorService: FormValidatorService, |
19 | private authService: AuthService, | 25 | private authService: AuthService, |
20 | private videoChannelValidatorsService: VideoChannelValidatorsService, | ||
21 | private notifier: Notifier, | 26 | private notifier: Notifier, |
22 | private router: Router, | 27 | private router: Router, |
23 | private videoChannelService: VideoChannelService | 28 | private videoChannelService: VideoChannelService |
@@ -31,10 +36,10 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE | |||
31 | 36 | ||
32 | ngOnInit () { | 37 | ngOnInit () { |
33 | this.buildForm({ | 38 | this.buildForm({ |
34 | name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME, | 39 | name: VIDEO_CHANNEL_NAME_VALIDATOR, |
35 | 'display-name': this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME, | 40 | 'display-name': VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, |
36 | description: this.videoChannelValidatorsService.VIDEO_CHANNEL_DESCRIPTION, | 41 | description: VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, |
37 | support: this.videoChannelValidatorsService.VIDEO_CHANNEL_SUPPORT | 42 | support: VIDEO_CHANNEL_SUPPORT_VALIDATOR |
38 | }) | 43 | }) |
39 | } | 44 | } |
40 | 45 | ||
diff --git a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-update.component.ts index 485521dcc..01659b8da 100644 --- a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-update.component.ts +++ b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channel-update.component.ts | |||
@@ -2,7 +2,12 @@ import { Subscription } from 'rxjs' | |||
2 | import { Component, OnDestroy, OnInit } from '@angular/core' | 2 | import { Component, OnDestroy, OnInit } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, Notifier, ServerService } from '@app/core' | 4 | import { AuthService, Notifier, ServerService } from '@app/core' |
5 | import { FormValidatorService, VideoChannelValidatorsService } from '@app/shared/shared-forms' | 5 | import { |
6 | VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, | ||
7 | VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, | ||
8 | VIDEO_CHANNEL_SUPPORT_VALIDATOR | ||
9 | } from '@app/shared/form-validators/video-channel-validators' | ||
10 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
6 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 11 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' |
7 | import { ServerConfig, VideoChannelUpdate } from '@shared/models' | 12 | import { ServerConfig, VideoChannelUpdate } from '@shared/models' |
8 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' | 13 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' |
@@ -23,7 +28,6 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
23 | constructor ( | 28 | constructor ( |
24 | protected formValidatorService: FormValidatorService, | 29 | protected formValidatorService: FormValidatorService, |
25 | private authService: AuthService, | 30 | private authService: AuthService, |
26 | private videoChannelValidatorsService: VideoChannelValidatorsService, | ||
27 | private notifier: Notifier, | 31 | private notifier: Notifier, |
28 | private router: Router, | 32 | private router: Router, |
29 | private route: ActivatedRoute, | 33 | private route: ActivatedRoute, |
@@ -39,9 +43,9 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
39 | .subscribe(config => this.serverConfig = config) | 43 | .subscribe(config => this.serverConfig = config) |
40 | 44 | ||
41 | this.buildForm({ | 45 | this.buildForm({ |
42 | 'display-name': this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME, | 46 | 'display-name': VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, |
43 | description: this.videoChannelValidatorsService.VIDEO_CHANNEL_DESCRIPTION, | 47 | description: VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, |
44 | support: this.videoChannelValidatorsService.VIDEO_CHANNEL_SUPPORT, | 48 | support: VIDEO_CHANNEL_SUPPORT_VALIDATOR, |
45 | bulkVideosSupportUpdate: null | 49 | bulkVideosSupportUpdate: null |
46 | }) | 50 | }) |
47 | 51 | ||
diff --git a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels-routing.module.ts b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels-routing.module.ts index 94037e18f..3aa3e360f 100644 --- a/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels-routing.module.ts +++ b/client/src/app/+my-account/+my-account-video-channels/my-account-video-channels-routing.module.ts | |||
@@ -10,7 +10,7 @@ const myAccountVideoChannelsRoutes: Routes = [ | |||
10 | component: MyAccountVideoChannelsComponent, | 10 | component: MyAccountVideoChannelsComponent, |
11 | data: { | 11 | data: { |
12 | meta: { | 12 | meta: { |
13 | title: 'Account video channels' | 13 | title: $localize`Account video channels` |
14 | } | 14 | } |
15 | } | 15 | } |
16 | }, | 16 | }, |
@@ -19,7 +19,7 @@ const myAccountVideoChannelsRoutes: Routes = [ | |||
19 | component: MyAccountVideoChannelCreateComponent, | 19 | component: MyAccountVideoChannelCreateComponent, |
20 | data: { | 20 | data: { |
21 | meta: { | 21 | meta: { |
22 | title: 'Create new video channel' | 22 | title: $localize`Create new video channel` |
23 | } | 23 | } |
24 | } | 24 | } |
25 | }, | 25 | }, |
@@ -28,7 +28,7 @@ const myAccountVideoChannelsRoutes: Routes = [ | |||
28 | component: MyAccountVideoChannelUpdateComponent, | 28 | component: MyAccountVideoChannelUpdateComponent, |
29 | data: { | 29 | data: { |
30 | meta: { | 30 | meta: { |
31 | title: 'Update video channel' | 31 | title: $localize`Update video channel` |
32 | } | 32 | } |
33 | } | 33 | } |
34 | } | 34 | } |
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts index 3bfffe2da..4c4436755 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts +++ b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { AuthService, Notifier } from '@app/core' | 2 | import { AuthService, Notifier } from '@app/core' |
3 | import { FormReactive, FormValidatorService, VideoAcceptOwnershipValidatorsService } from '@app/shared/shared-forms' | 3 | import { OWNERSHIP_CHANGE_CHANNEL_VALIDATOR } from '@app/shared/form-validators/video-ownership-change-validators' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
4 | import { VideoChannelService, VideoOwnershipService } from '@app/shared/shared-main' | 5 | import { VideoChannelService, VideoOwnershipService } from '@app/shared/shared-main' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { VideoChangeOwnership, VideoChannel } from '@shared/models' | 7 | import { VideoChangeOwnership, VideoChannel } from '@shared/models' |
@@ -23,7 +24,6 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O | |||
23 | 24 | ||
24 | constructor ( | 25 | constructor ( |
25 | protected formValidatorService: FormValidatorService, | 26 | protected formValidatorService: FormValidatorService, |
26 | private videoChangeOwnershipValidatorsService: VideoAcceptOwnershipValidatorsService, | ||
27 | private videoOwnershipService: VideoOwnershipService, | 27 | private videoOwnershipService: VideoOwnershipService, |
28 | private notifier: Notifier, | 28 | private notifier: Notifier, |
29 | private authService: AuthService, | 29 | private authService: AuthService, |
@@ -40,7 +40,7 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O | |||
40 | .subscribe(videoChannels => this.videoChannels = videoChannels.data) | 40 | .subscribe(videoChannels => this.videoChannels = videoChannels.data) |
41 | 41 | ||
42 | this.buildForm({ | 42 | this.buildForm({ |
43 | channel: this.videoChangeOwnershipValidatorsService.CHANNEL | 43 | channel: OWNERSHIP_CHANGE_CHANNEL_VALIDATOR |
44 | }) | 44 | }) |
45 | } | 45 | } |
46 | 46 | ||
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts index 48237e133..0bcb38ef5 100644 --- a/client/src/app/+my-account/my-account-routing.module.ts +++ b/client/src/app/+my-account/my-account-routing.module.ts | |||
@@ -34,7 +34,7 @@ const myAccountRoutes: Routes = [ | |||
34 | component: MyAccountSettingsComponent, | 34 | component: MyAccountSettingsComponent, |
35 | data: { | 35 | data: { |
36 | meta: { | 36 | meta: { |
37 | title: 'Account settings' | 37 | title: $localize`Account settings` |
38 | } | 38 | } |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -52,7 +52,7 @@ const myAccountRoutes: Routes = [ | |||
52 | component: MyAccountVideoPlaylistsComponent, | 52 | component: MyAccountVideoPlaylistsComponent, |
53 | data: { | 53 | data: { |
54 | meta: { | 54 | meta: { |
55 | title: 'Account playlists' | 55 | title: $localize`Account playlists` |
56 | } | 56 | } |
57 | } | 57 | } |
58 | }, | 58 | }, |
@@ -61,7 +61,7 @@ const myAccountRoutes: Routes = [ | |||
61 | component: MyAccountVideoPlaylistCreateComponent, | 61 | component: MyAccountVideoPlaylistCreateComponent, |
62 | data: { | 62 | data: { |
63 | meta: { | 63 | meta: { |
64 | title: 'Create new playlist' | 64 | title: $localize`Create new playlist` |
65 | } | 65 | } |
66 | } | 66 | } |
67 | }, | 67 | }, |
@@ -70,7 +70,7 @@ const myAccountRoutes: Routes = [ | |||
70 | component: MyAccountVideoPlaylistElementsComponent, | 70 | component: MyAccountVideoPlaylistElementsComponent, |
71 | data: { | 71 | data: { |
72 | meta: { | 72 | meta: { |
73 | title: 'Playlist elements' | 73 | title: $localize`Playlist elements` |
74 | } | 74 | } |
75 | } | 75 | } |
76 | }, | 76 | }, |
@@ -79,7 +79,7 @@ const myAccountRoutes: Routes = [ | |||
79 | component: MyAccountVideoPlaylistUpdateComponent, | 79 | component: MyAccountVideoPlaylistUpdateComponent, |
80 | data: { | 80 | data: { |
81 | meta: { | 81 | meta: { |
82 | title: 'Update playlist' | 82 | title: $localize`Update playlist` |
83 | } | 83 | } |
84 | } | 84 | } |
85 | }, | 85 | }, |
@@ -89,7 +89,7 @@ const myAccountRoutes: Routes = [ | |||
89 | component: MyAccountVideosComponent, | 89 | component: MyAccountVideosComponent, |
90 | data: { | 90 | data: { |
91 | meta: { | 91 | meta: { |
92 | title: 'Account videos' | 92 | title: $localize`Account videos` |
93 | }, | 93 | }, |
94 | reuse: { | 94 | reuse: { |
95 | enabled: true, | 95 | enabled: true, |
@@ -102,7 +102,7 @@ const myAccountRoutes: Routes = [ | |||
102 | component: MyAccountVideoImportsComponent, | 102 | component: MyAccountVideoImportsComponent, |
103 | data: { | 103 | data: { |
104 | meta: { | 104 | meta: { |
105 | title: 'Account video imports' | 105 | title: $localize`Account video imports` |
106 | } | 106 | } |
107 | } | 107 | } |
108 | }, | 108 | }, |
@@ -111,7 +111,7 @@ const myAccountRoutes: Routes = [ | |||
111 | component: MyAccountSubscriptionsComponent, | 111 | component: MyAccountSubscriptionsComponent, |
112 | data: { | 112 | data: { |
113 | meta: { | 113 | meta: { |
114 | title: 'Account subscriptions' | 114 | title: $localize`Account subscriptions` |
115 | } | 115 | } |
116 | } | 116 | } |
117 | }, | 117 | }, |
@@ -120,7 +120,7 @@ const myAccountRoutes: Routes = [ | |||
120 | component: MyAccountOwnershipComponent, | 120 | component: MyAccountOwnershipComponent, |
121 | data: { | 121 | data: { |
122 | meta: { | 122 | meta: { |
123 | title: 'Ownership changes' | 123 | title: $localize`Ownership changes` |
124 | } | 124 | } |
125 | } | 125 | } |
126 | }, | 126 | }, |
@@ -129,7 +129,7 @@ const myAccountRoutes: Routes = [ | |||
129 | component: MyAccountBlocklistComponent, | 129 | component: MyAccountBlocklistComponent, |
130 | data: { | 130 | data: { |
131 | meta: { | 131 | meta: { |
132 | title: 'Muted accounts' | 132 | title: $localize`Muted accounts` |
133 | } | 133 | } |
134 | } | 134 | } |
135 | }, | 135 | }, |
@@ -138,7 +138,7 @@ const myAccountRoutes: Routes = [ | |||
138 | component: MyAccountServerBlocklistComponent, | 138 | component: MyAccountServerBlocklistComponent, |
139 | data: { | 139 | data: { |
140 | meta: { | 140 | meta: { |
141 | title: 'Muted servers' | 141 | title: $localize`Muted servers` |
142 | } | 142 | } |
143 | } | 143 | } |
144 | }, | 144 | }, |
@@ -147,7 +147,7 @@ const myAccountRoutes: Routes = [ | |||
147 | component: MyAccountHistoryComponent, | 147 | component: MyAccountHistoryComponent, |
148 | data: { | 148 | data: { |
149 | meta: { | 149 | meta: { |
150 | title: 'Videos history' | 150 | title: $localize`Videos history` |
151 | }, | 151 | }, |
152 | reuse: { | 152 | reuse: { |
153 | enabled: true, | 153 | enabled: true, |
@@ -160,7 +160,7 @@ const myAccountRoutes: Routes = [ | |||
160 | component: MyAccountNotificationsComponent, | 160 | component: MyAccountNotificationsComponent, |
161 | data: { | 161 | data: { |
162 | meta: { | 162 | meta: { |
163 | title: 'Notifications' | 163 | title: $localize`Notifications` |
164 | } | 164 | } |
165 | } | 165 | } |
166 | }, | 166 | }, |
@@ -169,7 +169,7 @@ const myAccountRoutes: Routes = [ | |||
169 | component: MyAccountAbusesListComponent, | 169 | component: MyAccountAbusesListComponent, |
170 | data: { | 170 | data: { |
171 | meta: { | 171 | meta: { |
172 | title: 'My abuse reports' | 172 | title: $localize`My abuse reports` |
173 | } | 173 | } |
174 | } | 174 | } |
175 | } | 175 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts index 396936ef3..b2b7849c2 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts | |||
@@ -2,7 +2,8 @@ import { forkJoin } from 'rxjs' | |||
2 | import { tap } from 'rxjs/operators' | 2 | import { tap } from 'rxjs/operators' |
3 | import { Component, OnInit } from '@angular/core' | 3 | import { Component, OnInit } from '@angular/core' |
4 | import { AuthService, ServerService, UserService } from '@app/core' | 4 | import { AuthService, ServerService, UserService } from '@app/core' |
5 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 5 | import { USER_EMAIL_VALIDATOR, USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' |
6 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
6 | import { User } from '@shared/models' | 7 | import { User } from '@shared/models' |
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
@@ -17,18 +18,17 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni | |||
17 | 18 | ||
18 | constructor ( | 19 | constructor ( |
19 | protected formValidatorService: FormValidatorService, | 20 | protected formValidatorService: FormValidatorService, |
20 | private userValidatorsService: UserValidatorsService, | ||
21 | private authService: AuthService, | 21 | private authService: AuthService, |
22 | private userService: UserService, | 22 | private userService: UserService, |
23 | private serverService: ServerService | 23 | private serverService: ServerService |
24 | ) { | 24 | ) { |
25 | super() | 25 | super() |
26 | } | 26 | } |
27 | 27 | ||
28 | ngOnInit () { | 28 | ngOnInit () { |
29 | this.buildForm({ | 29 | this.buildForm({ |
30 | 'new-email': this.userValidatorsService.USER_EMAIL, | 30 | 'new-email': USER_EMAIL_VALIDATOR, |
31 | 'password': this.userValidatorsService.USER_PASSWORD | 31 | 'password': USER_PASSWORD_VALIDATOR |
32 | }) | 32 | }) |
33 | 33 | ||
34 | this.user = this.authService.getUser() | 34 | this.user = this.authService.getUser() |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts index 91fe4ec72..ba68bab32 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { filter } from 'rxjs/operators' | 1 | import { filter } from 'rxjs/operators' |
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, OnInit } from '@angular/core' |
3 | import { AuthService, Notifier, UserService } from '@app/core' | 3 | import { AuthService, Notifier, UserService } from '@app/core' |
4 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 4 | import { USER_CONFIRM_PASSWORD_VALIDATOR, USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' |
5 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { User } from '@shared/models' | 6 | import { User } from '@shared/models' |
6 | 7 | ||
7 | @Component({ | 8 | @Component({ |
@@ -15,7 +16,6 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On | |||
15 | 16 | ||
16 | constructor ( | 17 | constructor ( |
17 | protected formValidatorService: FormValidatorService, | 18 | protected formValidatorService: FormValidatorService, |
18 | private userValidatorsService: UserValidatorsService, | ||
19 | private notifier: Notifier, | 19 | private notifier: Notifier, |
20 | private authService: AuthService, | 20 | private authService: AuthService, |
21 | private userService: UserService | 21 | private userService: UserService |
@@ -25,9 +25,9 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On | |||
25 | 25 | ||
26 | ngOnInit () { | 26 | ngOnInit () { |
27 | this.buildForm({ | 27 | this.buildForm({ |
28 | 'current-password': this.userValidatorsService.USER_PASSWORD, | 28 | 'current-password': USER_PASSWORD_VALIDATOR, |
29 | 'new-password': this.userValidatorsService.USER_PASSWORD, | 29 | 'new-password': USER_PASSWORD_VALIDATOR, |
30 | 'new-confirmed-password': this.userValidatorsService.USER_CONFIRM_PASSWORD | 30 | 'new-confirmed-password': USER_CONFIRM_PASSWORD_VALIDATOR |
31 | }) | 31 | }) |
32 | 32 | ||
33 | this.user = this.authService.getUser() | 33 | this.user = this.authService.getUser() |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts index ed0984bf7..000a2c0ac 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { Subject } from 'rxjs' | 1 | import { Subject } from 'rxjs' |
2 | import { Component, Input, OnInit } from '@angular/core' | 2 | import { Component, Input, OnInit } from '@angular/core' |
3 | import { Notifier, User, UserService } from '@app/core' | 3 | import { Notifier, User, UserService } from '@app/core' |
4 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 4 | import { USER_DESCRIPTION_VALIDATOR, USER_DISPLAY_NAME_REQUIRED_VALIDATOR } from '@app/shared/form-validators/user-validators' |
5 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | 6 | ||
6 | @Component({ | 7 | @Component({ |
7 | selector: 'my-account-profile', | 8 | selector: 'my-account-profile', |
@@ -16,17 +17,16 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit { | |||
16 | 17 | ||
17 | constructor ( | 18 | constructor ( |
18 | protected formValidatorService: FormValidatorService, | 19 | protected formValidatorService: FormValidatorService, |
19 | private userValidatorsService: UserValidatorsService, | ||
20 | private notifier: Notifier, | 20 | private notifier: Notifier, |
21 | private userService: UserService | 21 | private userService: UserService |
22 | ) { | 22 | ) { |
23 | super() | 23 | super() |
24 | } | 24 | } |
25 | 25 | ||
26 | ngOnInit () { | 26 | ngOnInit () { |
27 | this.buildForm({ | 27 | this.buildForm({ |
28 | 'display-name': this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED, | 28 | 'display-name': USER_DISPLAY_NAME_REQUIRED_VALIDATOR, |
29 | description: this.userValidatorsService.USER_DESCRIPTION | 29 | description: USER_DESCRIPTION_VALIDATOR |
30 | }) | 30 | }) |
31 | 31 | ||
32 | this.userInformationLoaded.subscribe(() => { | 32 | this.userInformationLoaded.subscribe(() => { |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts index 5427dc3a0..7a80aaa92 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts | |||
@@ -2,7 +2,14 @@ import { Component, OnInit } from '@angular/core' | |||
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { AuthService, Notifier, ServerService } from '@app/core' | 3 | import { AuthService, Notifier, ServerService } from '@app/core' |
4 | import { populateAsyncUserVideoChannels } from '@app/helpers' | 4 | import { populateAsyncUserVideoChannels } from '@app/helpers' |
5 | import { FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/shared-forms' | 5 | import { |
6 | setPlaylistChannelValidator, | ||
7 | VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR, | ||
8 | VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR, | ||
9 | VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR, | ||
10 | VIDEO_PLAYLIST_PRIVACY_VALIDATOR | ||
11 | } from '@app/shared/form-validators/video-playlist-validators' | ||
12 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
6 | import { VideoPlaylistService } from '@app/shared/shared-video-playlist' | 13 | import { VideoPlaylistService } from '@app/shared/shared-video-playlist' |
7 | import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' | 14 | import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' |
8 | import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' | 15 | import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' |
@@ -19,7 +26,6 @@ export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylis | |||
19 | constructor ( | 26 | constructor ( |
20 | protected formValidatorService: FormValidatorService, | 27 | protected formValidatorService: FormValidatorService, |
21 | private authService: AuthService, | 28 | private authService: AuthService, |
22 | private videoPlaylistValidatorsService: VideoPlaylistValidatorsService, | ||
23 | private notifier: Notifier, | 29 | private notifier: Notifier, |
24 | private router: Router, | 30 | private router: Router, |
25 | private videoPlaylistService: VideoPlaylistService, | 31 | private videoPlaylistService: VideoPlaylistService, |
@@ -30,15 +36,15 @@ export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylis | |||
30 | 36 | ||
31 | ngOnInit () { | 37 | ngOnInit () { |
32 | this.buildForm({ | 38 | this.buildForm({ |
33 | displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME, | 39 | displayName: VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR, |
34 | privacy: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_PRIVACY, | 40 | privacy: VIDEO_PLAYLIST_PRIVACY_VALIDATOR, |
35 | description: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DESCRIPTION, | 41 | description: VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR, |
36 | videoChannelId: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_CHANNEL_ID, | 42 | videoChannelId: VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR, |
37 | thumbnailfile: null | 43 | thumbnailfile: null |
38 | }) | 44 | }) |
39 | 45 | ||
40 | this.form.get('privacy').valueChanges.subscribe(privacy => { | 46 | this.form.get('privacy').valueChanges.subscribe(privacy => { |
41 | this.videoPlaylistValidatorsService.setChannelValidator(this.form.get('videoChannelId'), privacy) | 47 | setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy) |
42 | }) | 48 | }) |
43 | 49 | ||
44 | populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) | 50 | populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss index 3204167ff..de7e1993f 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss | |||
@@ -65,12 +65,19 @@ | |||
65 | padding-top: 20px; | 65 | padding-top: 20px; |
66 | margin-left: calc(#{var(--expanded-horizontal-margin-content)} * -1); | 66 | margin-left: calc(#{var(--expanded-horizontal-margin-content)} * -1); |
67 | } | 67 | } |
68 | } | ||
69 | 68 | ||
70 | @media not all and (hover: hover) and (pointer: fine) { | 69 | .playlist-elements { |
71 | .video { | 70 | padding: 0 !important; |
72 | .more { | 71 | } |
73 | opacity: 1; | 72 | |
73 | ::ng-deep my-video-playlist-element-miniature { | ||
74 | |||
75 | .video { | ||
76 | padding: 5px !important; | ||
77 | } | ||
78 | |||
79 | .position { | ||
80 | margin-right: 5px !important; | ||
74 | } | 81 | } |
75 | } | 82 | } |
76 | } | 83 | } |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts index 149d0d94f..fefc6d607 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts | |||
@@ -4,7 +4,14 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, Notifier, ServerService } from '@app/core' | 5 | import { AuthService, Notifier, ServerService } from '@app/core' |
6 | import { populateAsyncUserVideoChannels } from '@app/helpers' | 6 | import { populateAsyncUserVideoChannels } from '@app/helpers' |
7 | import { FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/shared-forms' | 7 | import { |
8 | setPlaylistChannelValidator, | ||
9 | VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR, | ||
10 | VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR, | ||
11 | VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR, | ||
12 | VIDEO_PLAYLIST_PRIVACY_VALIDATOR | ||
13 | } from '@app/shared/form-validators/video-playlist-validators' | ||
14 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
8 | import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' | 15 | import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' |
9 | import { VideoPlaylistUpdate } from '@shared/models' | 16 | import { VideoPlaylistUpdate } from '@shared/models' |
10 | import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit' | 17 | import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit' |
@@ -23,7 +30,6 @@ export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylis | |||
23 | constructor ( | 30 | constructor ( |
24 | protected formValidatorService: FormValidatorService, | 31 | protected formValidatorService: FormValidatorService, |
25 | private authService: AuthService, | 32 | private authService: AuthService, |
26 | private videoPlaylistValidatorsService: VideoPlaylistValidatorsService, | ||
27 | private notifier: Notifier, | 33 | private notifier: Notifier, |
28 | private router: Router, | 34 | private router: Router, |
29 | private route: ActivatedRoute, | 35 | private route: ActivatedRoute, |
@@ -35,15 +41,15 @@ export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylis | |||
35 | 41 | ||
36 | ngOnInit () { | 42 | ngOnInit () { |
37 | this.buildForm({ | 43 | this.buildForm({ |
38 | displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME, | 44 | displayName: VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR, |
39 | privacy: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_PRIVACY, | 45 | privacy: VIDEO_PLAYLIST_PRIVACY_VALIDATOR, |
40 | description: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DESCRIPTION, | 46 | description: VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR, |
41 | videoChannelId: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_CHANNEL_ID, | 47 | videoChannelId: VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR, |
42 | thumbnailfile: null | 48 | thumbnailfile: null |
43 | }) | 49 | }) |
44 | 50 | ||
45 | this.form.get('privacy').valueChanges.subscribe(privacy => { | 51 | this.form.get('privacy').valueChanges.subscribe(privacy => { |
46 | this.videoPlaylistValidatorsService.setChannelValidator(this.form.get('videoChannelId'), privacy) | 52 | setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy) |
47 | }) | 53 | }) |
48 | 54 | ||
49 | populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) | 55 | populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) |
diff --git a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts index edd691694..84237dee1 100644 --- a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts +++ b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' |
2 | import { Notifier, UserService } from '@app/core' | 2 | import { Notifier, UserService } from '@app/core' |
3 | import { FormReactive, FormValidatorService, VideoChangeOwnershipValidatorsService } from '@app/shared/shared-forms' | 3 | import { OWNERSHIP_CHANGE_USERNAME_VALIDATOR } from '@app/shared/form-validators/video-ownership-change-validators' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
4 | import { Video, VideoOwnershipService } from '@app/shared/shared-main' | 5 | import { Video, VideoOwnershipService } from '@app/shared/shared-main' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | 7 | ||
@@ -20,7 +21,6 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni | |||
20 | 21 | ||
21 | constructor ( | 22 | constructor ( |
22 | protected formValidatorService: FormValidatorService, | 23 | protected formValidatorService: FormValidatorService, |
23 | private videoChangeOwnershipValidatorsService: VideoChangeOwnershipValidatorsService, | ||
24 | private videoOwnershipService: VideoOwnershipService, | 24 | private videoOwnershipService: VideoOwnershipService, |
25 | private notifier: Notifier, | 25 | private notifier: Notifier, |
26 | private userService: UserService, | 26 | private userService: UserService, |
@@ -31,7 +31,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni | |||
31 | 31 | ||
32 | ngOnInit () { | 32 | ngOnInit () { |
33 | this.buildForm({ | 33 | this.buildForm({ |
34 | username: this.videoChangeOwnershipValidatorsService.USERNAME | 34 | username: OWNERSHIP_CHANGE_USERNAME_VALIDATOR |
35 | }) | 35 | }) |
36 | this.usernamePropositions = [] | 36 | this.usernamePropositions = [] |
37 | } | 37 | } |
diff --git a/client/src/app/+page-not-found/page-not-found-routing.module.ts b/client/src/app/+page-not-found/page-not-found-routing.module.ts index e3407099d..11399fbfd 100644 --- a/client/src/app/+page-not-found/page-not-found-routing.module.ts +++ b/client/src/app/+page-not-found/page-not-found-routing.module.ts | |||
@@ -10,7 +10,7 @@ const pageNotFoundRoutes: Routes = [ | |||
10 | canActivate: [ MetaGuard ], | 10 | canActivate: [ MetaGuard ], |
11 | data: { | 11 | data: { |
12 | meta: { | 12 | meta: { |
13 | title: 'Not found' | 13 | title: $localize`Not found` |
14 | } | 14 | } |
15 | } | 15 | } |
16 | } | 16 | } |
diff --git a/client/src/app/+reset-password/reset-password-routing.module.ts b/client/src/app/+reset-password/reset-password-routing.module.ts index 31bc08709..7f1ba2f68 100644 --- a/client/src/app/+reset-password/reset-password-routing.module.ts +++ b/client/src/app/+reset-password/reset-password-routing.module.ts | |||
@@ -10,7 +10,7 @@ const resetPasswordRoutes: Routes = [ | |||
10 | canActivate: [ MetaGuard ], | 10 | canActivate: [ MetaGuard ], |
11 | data: { | 11 | data: { |
12 | meta: { | 12 | meta: { |
13 | title: 'Reset password' | 13 | title: `Reset password` |
14 | } | 14 | } |
15 | } | 15 | } |
16 | } | 16 | } |
diff --git a/client/src/app/+reset-password/reset-password.component.ts b/client/src/app/+reset-password/reset-password.component.ts index 16e4f4090..ce9144170 100644 --- a/client/src/app/+reset-password/reset-password.component.ts +++ b/client/src/app/+reset-password/reset-password.component.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Notifier, UserService } from '@app/core' | 3 | import { Notifier, UserService } from '@app/core' |
4 | import { FormReactive, FormValidatorService, ResetPasswordValidatorsService, UserValidatorsService } from '@app/shared/shared-forms' | 4 | import { RESET_PASSWORD_CONFIRM_VALIDATOR } from '@app/shared/form-validators/reset-password-validators' |
5 | import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' | ||
6 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | 7 | ||
6 | @Component({ | 8 | @Component({ |
7 | selector: 'my-login', | 9 | selector: 'my-login', |
@@ -15,8 +17,6 @@ export class ResetPasswordComponent extends FormReactive implements OnInit { | |||
15 | 17 | ||
16 | constructor ( | 18 | constructor ( |
17 | protected formValidatorService: FormValidatorService, | 19 | protected formValidatorService: FormValidatorService, |
18 | private resetPasswordValidatorsService: ResetPasswordValidatorsService, | ||
19 | private userValidatorsService: UserValidatorsService, | ||
20 | private userService: UserService, | 20 | private userService: UserService, |
21 | private notifier: Notifier, | 21 | private notifier: Notifier, |
22 | private router: Router, | 22 | private router: Router, |
@@ -27,8 +27,8 @@ export class ResetPasswordComponent extends FormReactive implements OnInit { | |||
27 | 27 | ||
28 | ngOnInit () { | 28 | ngOnInit () { |
29 | this.buildForm({ | 29 | this.buildForm({ |
30 | password: this.userValidatorsService.USER_PASSWORD, | 30 | password: USER_PASSWORD_VALIDATOR, |
31 | 'password-confirm': this.resetPasswordValidatorsService.RESET_PASSWORD_CONFIRM | 31 | 'password-confirm': RESET_PASSWORD_CONFIRM_VALIDATOR |
32 | }) | 32 | }) |
33 | 33 | ||
34 | this.userId = this.route.snapshot.queryParams['userId'] | 34 | this.userId = this.route.snapshot.queryParams['userId'] |
diff --git a/client/src/app/+search/search-routing.module.ts b/client/src/app/+search/search-routing.module.ts index 14a0d0a13..e5d7d1ede 100644 --- a/client/src/app/+search/search-routing.module.ts +++ b/client/src/app/+search/search-routing.module.ts | |||
@@ -12,7 +12,7 @@ const searchRoutes: Routes = [ | |||
12 | canActivate: [ MetaGuard ], | 12 | canActivate: [ MetaGuard ], |
13 | data: { | 13 | data: { |
14 | meta: { | 14 | meta: { |
15 | title: 'Search' | 15 | title: $localize`Search` |
16 | } | 16 | } |
17 | } | 17 | } |
18 | }, | 18 | }, |
diff --git a/client/src/app/+signup/+register/register-routing.module.ts b/client/src/app/+signup/+register/register-routing.module.ts index 0deed8a9b..61a2fa42d 100644 --- a/client/src/app/+signup/+register/register-routing.module.ts +++ b/client/src/app/+signup/+register/register-routing.module.ts | |||
@@ -11,7 +11,7 @@ const registerRoutes: Routes = [ | |||
11 | canActivate: [ MetaGuard, UnloggedGuard ], | 11 | canActivate: [ MetaGuard, UnloggedGuard ], |
12 | data: { | 12 | data: { |
13 | meta: { | 13 | meta: { |
14 | title: 'Register' | 14 | title: $localize`Register` |
15 | } | 15 | } |
16 | }, | 16 | }, |
17 | resolve: { | 17 | resolve: { |
diff --git a/client/src/app/+signup/+register/register-step-channel.component.ts b/client/src/app/+signup/+register/register-step-channel.component.ts index 8a0120840..d965a7865 100644 --- a/client/src/app/+signup/+register/register-step-channel.component.ts +++ b/client/src/app/+signup/+register/register-step-channel.component.ts | |||
@@ -3,7 +3,8 @@ import { pairwise } from 'rxjs/operators' | |||
3 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | 3 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
4 | import { FormGroup } from '@angular/forms' | 4 | import { FormGroup } from '@angular/forms' |
5 | import { UserService } from '@app/core' | 5 | import { UserService } from '@app/core' |
6 | import { FormReactive, FormValidatorService, VideoChannelValidatorsService } from '@app/shared/shared-forms' | 6 | import { VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, VIDEO_CHANNEL_NAME_VALIDATOR } from '@app/shared/form-validators/video-channel-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-register-step-channel', | 10 | selector: 'my-register-step-channel', |
@@ -16,8 +17,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit | |||
16 | 17 | ||
17 | constructor ( | 18 | constructor ( |
18 | protected formValidatorService: FormValidatorService, | 19 | protected formValidatorService: FormValidatorService, |
19 | private userService: UserService, | 20 | private userService: UserService |
20 | private videoChannelValidatorsService: VideoChannelValidatorsService | ||
21 | ) { | 21 | ) { |
22 | super() | 22 | super() |
23 | } | 23 | } |
@@ -28,8 +28,8 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit | |||
28 | 28 | ||
29 | ngOnInit () { | 29 | ngOnInit () { |
30 | this.buildForm({ | 30 | this.buildForm({ |
31 | displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME, | 31 | displayName: VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, |
32 | name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME | 32 | name: VIDEO_CHANNEL_NAME_VALIDATOR |
33 | }) | 33 | }) |
34 | 34 | ||
35 | setTimeout(() => this.formBuilt.emit(this.form)) | 35 | setTimeout(() => this.formBuilt.emit(this.form)) |
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts index 3d9ab8b6b..65536568b 100644 --- a/client/src/app/+signup/+register/register-step-user.component.ts +++ b/client/src/app/+signup/+register/register-step-user.component.ts | |||
@@ -3,7 +3,14 @@ import { pairwise } from 'rxjs/operators' | |||
3 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | 3 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
4 | import { FormGroup } from '@angular/forms' | 4 | import { FormGroup } from '@angular/forms' |
5 | import { UserService } from '@app/core' | 5 | import { UserService } from '@app/core' |
6 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 6 | import { |
7 | USER_DISPLAY_NAME_REQUIRED_VALIDATOR, | ||
8 | USER_EMAIL_VALIDATOR, | ||
9 | USER_PASSWORD_VALIDATOR, | ||
10 | USER_TERMS_VALIDATOR, | ||
11 | USER_USERNAME_VALIDATOR | ||
12 | } from '@app/shared/form-validators/user-validators' | ||
13 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
7 | 14 | ||
8 | @Component({ | 15 | @Component({ |
9 | selector: 'my-register-step-user', | 16 | selector: 'my-register-step-user', |
@@ -19,8 +26,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit { | |||
19 | 26 | ||
20 | constructor ( | 27 | constructor ( |
21 | protected formValidatorService: FormValidatorService, | 28 | protected formValidatorService: FormValidatorService, |
22 | private userService: UserService, | 29 | private userService: UserService |
23 | private userValidatorsService: UserValidatorsService | ||
24 | ) { | 30 | ) { |
25 | super() | 31 | super() |
26 | } | 32 | } |
@@ -31,11 +37,11 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit { | |||
31 | 37 | ||
32 | ngOnInit () { | 38 | ngOnInit () { |
33 | this.buildForm({ | 39 | this.buildForm({ |
34 | displayName: this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED, | 40 | displayName: USER_DISPLAY_NAME_REQUIRED_VALIDATOR, |
35 | username: this.userValidatorsService.USER_USERNAME, | 41 | username: USER_USERNAME_VALIDATOR, |
36 | password: this.userValidatorsService.USER_PASSWORD, | 42 | password: USER_PASSWORD_VALIDATOR, |
37 | email: this.userValidatorsService.USER_EMAIL, | 43 | email: USER_EMAIL_VALIDATOR, |
38 | terms: this.userValidatorsService.USER_TERMS | 44 | terms: USER_TERMS_VALIDATOR |
39 | }) | 45 | }) |
40 | 46 | ||
41 | setTimeout(() => this.formBuilt.emit(this.form)) | 47 | setTimeout(() => this.formBuilt.emit(this.form)) |
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts index b26581d2b..830dd9962 100644 --- a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Notifier, RedirectService, ServerService, UserService } from '@app/core' | 2 | import { Notifier, RedirectService, ServerService, UserService } from '@app/core' |
3 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 3 | import { USER_EMAIL_VALIDATOR } from '@app/shared/form-validators/user-validators' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
4 | import { ServerConfig } from '@shared/models' | 5 | import { ServerConfig } from '@shared/models' |
5 | 6 | ||
6 | @Component({ | 7 | @Component({ |
@@ -14,7 +15,6 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements | |||
14 | 15 | ||
15 | constructor ( | 16 | constructor ( |
16 | protected formValidatorService: FormValidatorService, | 17 | protected formValidatorService: FormValidatorService, |
17 | private userValidatorsService: UserValidatorsService, | ||
18 | private userService: UserService, | 18 | private userService: UserService, |
19 | private serverService: ServerService, | 19 | private serverService: ServerService, |
20 | private notifier: Notifier, | 20 | private notifier: Notifier, |
@@ -33,7 +33,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements | |||
33 | .subscribe(config => this.serverConfig = config) | 33 | .subscribe(config => this.serverConfig = config) |
34 | 34 | ||
35 | this.buildForm({ | 35 | this.buildForm({ |
36 | 'verify-email-email': this.userValidatorsService.USER_EMAIL | 36 | 'verify-email-email': USER_EMAIL_VALIDATOR |
37 | }) | 37 | }) |
38 | } | 38 | } |
39 | 39 | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-routing.module.ts b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts index 16d5fe0d0..c9ac67e4c 100644 --- a/client/src/app/+signup/+verify-account/verify-account-routing.module.ts +++ b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts | |||
@@ -14,7 +14,7 @@ const verifyAccountRoutes: Routes = [ | |||
14 | component: VerifyAccountEmailComponent, | 14 | component: VerifyAccountEmailComponent, |
15 | data: { | 15 | data: { |
16 | meta: { | 16 | meta: { |
17 | title: 'Verify account email' | 17 | title: $localize`Verify account email` |
18 | } | 18 | } |
19 | } | 19 | } |
20 | }, | 20 | }, |
@@ -23,7 +23,7 @@ const verifyAccountRoutes: Routes = [ | |||
23 | component: VerifyAccountAskSendEmailComponent, | 23 | component: VerifyAccountAskSendEmailComponent, |
24 | data: { | 24 | data: { |
25 | meta: { | 25 | meta: { |
26 | title: 'Verify account ask send email' | 26 | title: $localize`Verify account ask send email` |
27 | } | 27 | } |
28 | } | 28 | } |
29 | } | 29 | } |
diff --git a/client/src/app/+video-channels/video-channels-routing.module.ts b/client/src/app/+video-channels/video-channels-routing.module.ts index e79e6a680..f8c32f14e 100644 --- a/client/src/app/+video-channels/video-channels-routing.module.ts +++ b/client/src/app/+video-channels/video-channels-routing.module.ts | |||
@@ -22,7 +22,7 @@ const videoChannelsRoutes: Routes = [ | |||
22 | component: VideoChannelVideosComponent, | 22 | component: VideoChannelVideosComponent, |
23 | data: { | 23 | data: { |
24 | meta: { | 24 | meta: { |
25 | title: 'Video channel videos' | 25 | title: $localize`Video channel videos` |
26 | }, | 26 | }, |
27 | reuse: { | 27 | reuse: { |
28 | enabled: true, | 28 | enabled: true, |
@@ -35,7 +35,7 @@ const videoChannelsRoutes: Routes = [ | |||
35 | component: VideoChannelPlaylistsComponent, | 35 | component: VideoChannelPlaylistsComponent, |
36 | data: { | 36 | data: { |
37 | meta: { | 37 | meta: { |
38 | title: 'Video channel playlists' | 38 | title: $localize`Video channel playlists` |
39 | } | 39 | } |
40 | } | 40 | } |
41 | }, | 41 | }, |
@@ -44,7 +44,7 @@ const videoChannelsRoutes: Routes = [ | |||
44 | component: VideoChannelAboutComponent, | 44 | component: VideoChannelAboutComponent, |
45 | data: { | 45 | data: { |
46 | meta: { | 46 | meta: { |
47 | title: 'About video channel' | 47 | title: $localize`About video channel` |
48 | } | 48 | } |
49 | } | 49 | } |
50 | } | 50 | } |
diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts index a90d04ce8..e48d16527 100644 --- a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { FormReactive, FormValidatorService, VideoCaptionsValidatorsService } from '@app/shared/shared-forms' | 3 | import { VIDEO_CAPTION_FILE_VALIDATOR, VIDEO_CAPTION_LANGUAGE_VALIDATOR } from '@app/shared/form-validators/video-captions-validators' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
4 | import { VideoCaptionEdit } from '@app/shared/shared-main' | 5 | import { VideoCaptionEdit } from '@app/shared/shared-main' |
5 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
6 | import { ServerConfig, VideoConstant } from '@shared/models' | 7 | import { ServerConfig, VideoConstant } from '@shared/models' |
@@ -27,8 +28,7 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
27 | constructor ( | 28 | constructor ( |
28 | protected formValidatorService: FormValidatorService, | 29 | protected formValidatorService: FormValidatorService, |
29 | private modalService: NgbModal, | 30 | private modalService: NgbModal, |
30 | private serverService: ServerService, | 31 | private serverService: ServerService |
31 | private videoCaptionsValidatorsService: VideoCaptionsValidatorsService | ||
32 | ) { | 32 | ) { |
33 | super() | 33 | super() |
34 | } | 34 | } |
@@ -46,8 +46,8 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni | |||
46 | .subscribe(languages => this.videoCaptionLanguages = languages) | 46 | .subscribe(languages => this.videoCaptionLanguages = languages) |
47 | 47 | ||
48 | this.buildForm({ | 48 | this.buildForm({ |
49 | language: this.videoCaptionsValidatorsService.VIDEO_CAPTION_LANGUAGE, | 49 | language: VIDEO_CAPTION_LANGUAGE_VALIDATOR, |
50 | captionfile: this.videoCaptionsValidatorsService.VIDEO_CAPTION_FILE | 50 | captionfile: VIDEO_CAPTION_FILE_VALIDATOR |
51 | }) | 51 | }) |
52 | } | 52 | } |
53 | 53 | ||
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html index ae3413e79..842997b20 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html | |||
@@ -265,6 +265,21 @@ | |||
265 | </ng-template> | 265 | </ng-template> |
266 | </ng-container> | 266 | </ng-container> |
267 | 267 | ||
268 | <ng-container ngbNavItem *ngIf="pluginFields.length !== 0"> | ||
269 | <a ngbNavLink i18n>Plugin settings</a> | ||
270 | |||
271 | <ng-template ngbNavContent> | ||
272 | <div class="row plugin-settings"> | ||
273 | |||
274 | <div class="col-md-12 col-xl-8"> | ||
275 | <div *ngFor="let pluginSetting of pluginFields" class="form-group"> | ||
276 | <my-dynamic-form-field [form]="pluginDataFormGroup" [formErrors]="formErrors" [setting]="pluginSetting.commonOptions"></my-dynamic-form-field> | ||
277 | </div> | ||
278 | </div> | ||
279 | |||
280 | </div> | ||
281 | </ng-template> | ||
282 | </ng-container> | ||
268 | </div> | 283 | </div> |
269 | 284 | ||
270 | <div [ngbNavOutlet]="nav"></div> | 285 | <div [ngbNavOutlet]="nav"></div> |
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.scss b/client/src/app/+videos/+video-edit/shared/video-edit.component.scss index 9caf009c5..3082a4f72 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.scss | |||
@@ -7,7 +7,8 @@ | |||
7 | @import 'variables'; | 7 | @import 'variables'; |
8 | @import 'mixins'; | 8 | @import 'mixins'; |
9 | 9 | ||
10 | label { | 10 | label, |
11 | my-dynamic-form-field ::ng-deep label { | ||
11 | font-weight: $font-regular; | 12 | font-weight: $font-regular; |
12 | font-size: 100%; | 13 | font-size: 100%; |
13 | } | 14 | } |
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index 050b6d931..f04111e69 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts | |||
@@ -1,13 +1,27 @@ | |||
1 | import { forkJoin } from 'rxjs' | 1 | import { forkJoin } from 'rxjs' |
2 | import { map } from 'rxjs/operators' | 2 | import { map } from 'rxjs/operators' |
3 | import { Component, Input, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' |
4 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' | 4 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' |
5 | import { ServerService } from '@app/core' | 5 | import { HooksService, PluginService, ServerService } from '@app/core' |
6 | import { removeElementFromArray } from '@app/helpers' | 6 | import { removeElementFromArray } from '@app/helpers' |
7 | import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem, VideoValidatorsService } from '@app/shared/shared-forms' | 7 | import { |
8 | VIDEO_CATEGORY_VALIDATOR, | ||
9 | VIDEO_CHANNEL_VALIDATOR, | ||
10 | VIDEO_DESCRIPTION_VALIDATOR, | ||
11 | VIDEO_LANGUAGE_VALIDATOR, | ||
12 | VIDEO_LICENCE_VALIDATOR, | ||
13 | VIDEO_NAME_VALIDATOR, | ||
14 | VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR, | ||
15 | VIDEO_PRIVACY_VALIDATOR, | ||
16 | VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR, | ||
17 | VIDEO_SUPPORT_VALIDATOR, | ||
18 | VIDEO_TAGS_ARRAY_VALIDATOR | ||
19 | } from '@app/shared/form-validators/video-validators' | ||
20 | import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' | ||
8 | import { InstanceService } from '@app/shared/shared-instance' | 21 | import { InstanceService } from '@app/shared/shared-instance' |
9 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' | 22 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' |
10 | import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' | 23 | import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' |
24 | import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' | ||
11 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' | 25 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' |
12 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 26 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
13 | 27 | ||
@@ -26,9 +40,12 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
26 | @Input() schedulePublicationPossible = true | 40 | @Input() schedulePublicationPossible = true |
27 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] | 41 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] |
28 | @Input() waitTranscodingEnabled = true | 42 | @Input() waitTranscodingEnabled = true |
43 | @Input() type: 'import-url' | 'import-torrent' | 'upload' | 'update' | ||
29 | 44 | ||
30 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent | 45 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent |
31 | 46 | ||
47 | @Output() pluginFieldsAdded = new EventEmitter<void>() | ||
48 | |||
32 | // So that it can be accessed in the template | 49 | // So that it can be accessed in the template |
33 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY | 50 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY |
34 | 51 | ||
@@ -40,6 +57,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
40 | tagValidators: ValidatorFn[] | 57 | tagValidators: ValidatorFn[] |
41 | tagValidatorsMessages: { [ name: string ]: string } | 58 | tagValidatorsMessages: { [ name: string ]: string } |
42 | 59 | ||
60 | pluginDataFormGroup: FormGroup | ||
61 | |||
43 | schedulePublicationEnabled = false | 62 | schedulePublicationEnabled = false |
44 | 63 | ||
45 | calendarLocale: any = {} | 64 | calendarLocale: any = {} |
@@ -51,18 +70,24 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
51 | 70 | ||
52 | serverConfig: ServerConfig | 71 | serverConfig: ServerConfig |
53 | 72 | ||
73 | pluginFields: { | ||
74 | commonOptions: RegisterClientFormFieldOptions | ||
75 | videoFormOptions: RegisterClientVideoFieldOptions | ||
76 | }[] = [] | ||
77 | |||
54 | private schedulerInterval: any | 78 | private schedulerInterval: any |
55 | private firstPatchDone = false | 79 | private firstPatchDone = false |
56 | private initialVideoCaptions: string[] = [] | 80 | private initialVideoCaptions: string[] = [] |
57 | 81 | ||
58 | constructor ( | 82 | constructor ( |
59 | private formValidatorService: FormValidatorService, | 83 | private formValidatorService: FormValidatorService, |
60 | private videoValidatorsService: VideoValidatorsService, | ||
61 | private videoService: VideoService, | 84 | private videoService: VideoService, |
62 | private serverService: ServerService, | 85 | private serverService: ServerService, |
86 | private pluginService: PluginService, | ||
63 | private instanceService: InstanceService, | 87 | private instanceService: InstanceService, |
64 | private i18nPrimengCalendarService: I18nPrimengCalendarService, | 88 | private i18nPrimengCalendarService: I18nPrimengCalendarService, |
65 | private ngZone: NgZone | 89 | private ngZone: NgZone, |
90 | private hooks: HooksService | ||
66 | ) { | 91 | ) { |
67 | this.calendarLocale = this.i18nPrimengCalendarService.getCalendarLocale() | 92 | this.calendarLocale = this.i18nPrimengCalendarService.getCalendarLocale() |
68 | this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone() | 93 | this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone() |
@@ -84,22 +109,22 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
84 | tags: [] | 109 | tags: [] |
85 | } | 110 | } |
86 | const obj: any = { | 111 | const obj: any = { |
87 | name: this.videoValidatorsService.VIDEO_NAME, | 112 | name: VIDEO_NAME_VALIDATOR, |
88 | privacy: this.videoValidatorsService.VIDEO_PRIVACY, | 113 | privacy: VIDEO_PRIVACY_VALIDATOR, |
89 | channelId: this.videoValidatorsService.VIDEO_CHANNEL, | 114 | channelId: VIDEO_CHANNEL_VALIDATOR, |
90 | nsfw: null, | 115 | nsfw: null, |
91 | commentsEnabled: null, | 116 | commentsEnabled: null, |
92 | downloadEnabled: null, | 117 | downloadEnabled: null, |
93 | waitTranscoding: null, | 118 | waitTranscoding: null, |
94 | category: this.videoValidatorsService.VIDEO_CATEGORY, | 119 | category: VIDEO_CATEGORY_VALIDATOR, |
95 | licence: this.videoValidatorsService.VIDEO_LICENCE, | 120 | licence: VIDEO_LICENCE_VALIDATOR, |
96 | language: this.videoValidatorsService.VIDEO_LANGUAGE, | 121 | language: VIDEO_LANGUAGE_VALIDATOR, |
97 | description: this.videoValidatorsService.VIDEO_DESCRIPTION, | 122 | description: VIDEO_DESCRIPTION_VALIDATOR, |
98 | tags: this.videoValidatorsService.VIDEO_TAGS_ARRAY, | 123 | tags: VIDEO_TAGS_ARRAY_VALIDATOR, |
99 | previewfile: null, | 124 | previewfile: null, |
100 | support: this.videoValidatorsService.VIDEO_SUPPORT, | 125 | support: VIDEO_SUPPORT_VALIDATOR, |
101 | schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT, | 126 | schedulePublicationAt: VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR, |
102 | originallyPublishedAt: this.videoValidatorsService.VIDEO_ORIGINALLY_PUBLISHED_AT | 127 | originallyPublishedAt: VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR |
103 | } | 128 | } |
104 | 129 | ||
105 | this.formValidatorService.updateForm( | 130 | this.formValidatorService.updateForm( |
@@ -124,19 +149,26 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
124 | ngOnInit () { | 149 | ngOnInit () { |
125 | this.updateForm() | 150 | this.updateForm() |
126 | 151 | ||
152 | this.pluginService.ensurePluginsAreLoaded('video-edit') | ||
153 | .then(() => this.updatePluginFields()) | ||
154 | |||
127 | this.serverService.getVideoCategories() | 155 | this.serverService.getVideoCategories() |
128 | .subscribe(res => this.videoCategories = res) | 156 | .subscribe(res => this.videoCategories = res) |
157 | |||
129 | this.serverService.getVideoLicences() | 158 | this.serverService.getVideoLicences() |
130 | .subscribe(res => this.videoLicences = res) | 159 | .subscribe(res => this.videoLicences = res) |
160 | |||
131 | forkJoin([ | 161 | forkJoin([ |
132 | this.instanceService.getAbout(), | 162 | this.instanceService.getAbout(), |
133 | this.serverService.getVideoLanguages() | 163 | this.serverService.getVideoLanguages() |
134 | ]).pipe(map(([ about, languages ]) => ({ about, languages }))) | 164 | ]).pipe(map(([ about, languages ]) => ({ about, languages }))) |
135 | .subscribe(res => { | 165 | .subscribe(res => { |
136 | this.videoLanguages = res.languages | 166 | this.videoLanguages = res.languages |
137 | .map(l => res.about.instance.languages.includes(l.id) | 167 | .map(l => { |
138 | ? { ...l, group: $localize`Instance languages`, groupOrder: 0 } | 168 | return res.about.instance.languages.includes(l.id) |
139 | : { ...l, group: $localize`All languages`, groupOrder: 1 }) | 169 | ? { ...l, group: $localize`Instance languages`, groupOrder: 0 } |
170 | : { ...l, group: $localize`All languages`, groupOrder: 1 } | ||
171 | }) | ||
140 | .sort((a, b) => a.groupOrder - b.groupOrder) | 172 | .sort((a, b) => a.groupOrder - b.groupOrder) |
141 | }) | 173 | }) |
142 | 174 | ||
@@ -161,6 +193,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
161 | this.ngZone.runOutsideAngular(() => { | 193 | this.ngZone.runOutsideAngular(() => { |
162 | this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute | 194 | this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute |
163 | }) | 195 | }) |
196 | |||
197 | this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type }) | ||
164 | } | 198 | } |
165 | 199 | ||
166 | ngOnDestroy () { | 200 | ngOnDestroy () { |
@@ -211,6 +245,23 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
211 | }) | 245 | }) |
212 | } | 246 | } |
213 | 247 | ||
248 | private updatePluginFields () { | ||
249 | this.pluginFields = this.pluginService.getRegisteredVideoFormFields(this.type) | ||
250 | |||
251 | if (this.pluginFields.length === 0) return | ||
252 | |||
253 | const obj: any = {} | ||
254 | |||
255 | for (const setting of this.pluginFields) { | ||
256 | obj[setting.commonOptions.name] = new FormControl(setting.commonOptions.default) | ||
257 | } | ||
258 | |||
259 | this.pluginDataFormGroup = new FormGroup(obj) | ||
260 | this.form.addControl('pluginData', this.pluginDataFormGroup) | ||
261 | |||
262 | this.pluginFieldsAdded.emit() | ||
263 | } | ||
264 | |||
214 | private trackPrivacyChange () { | 265 | private trackPrivacyChange () { |
215 | // We will update the schedule input and the wait transcoding checkbox validators | 266 | // We will update the schedule input and the wait transcoding checkbox validators |
216 | this.form.controls[ 'privacy' ] | 267 | this.form.controls[ 'privacy' ] |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html index 8db37a293..785514c76 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html | |||
@@ -14,7 +14,7 @@ | |||
14 | <my-help> | 14 | <my-help> |
15 | <ng-template ptTemplate="customHtml"> | 15 | <ng-template ptTemplate="customHtml"> |
16 | <ng-container i18n> | 16 | <ng-container i18n> |
17 | You can import any torrent file that points to a mp4 file. | 17 | You can import any torrent file that points to a media file. |
18 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | 18 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. |
19 | </ng-container> | 19 | </ng-container> |
20 | </ng-template> | 20 | </ng-template> |
@@ -58,6 +58,7 @@ | |||
58 | <my-video-edit | 58 | <my-video-edit |
59 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" | 59 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" |
60 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" | 60 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" |
61 | type="import-torrent" | ||
61 | ></my-video-edit> | 62 | ></my-video-edit> |
62 | 63 | ||
63 | <div class="submit-container"> | 64 | <div class="submit-container"> |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html index 9b5cc3361..3e4eb5fbc 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <ng-template ptTemplate="customHtml"> | 9 | <ng-template ptTemplate="customHtml"> |
10 | <ng-container i18n> | 10 | <ng-container i18n> |
11 | You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> | 11 | You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> |
12 | or URL that points to a raw MP4 file. | 12 | or URL that points to a media file. |
13 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | 13 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. |
14 | </ng-container> | 14 | </ng-container> |
15 | </ng-template> | 15 | </ng-template> |
@@ -54,6 +54,7 @@ | |||
54 | <my-video-edit | 54 | <my-video-edit |
55 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" | 55 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" |
56 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" | 56 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" |
57 | type="import-url" | ||
57 | ></my-video-edit> | 58 | ></my-video-edit> |
58 | 59 | ||
59 | <div class="submit-container"> | 60 | <div class="submit-container"> |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html index ed697c25b..677fa1197 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html | |||
@@ -69,6 +69,7 @@ | |||
69 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" | 69 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" |
70 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" | 70 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" |
71 | [waitTranscodingEnabled]="waitTranscodingEnabled" | 71 | [waitTranscodingEnabled]="waitTranscodingEnabled" |
72 | type="upload" | ||
72 | ></my-video-edit> | 73 | ></my-video-edit> |
73 | 74 | ||
74 | <div class="submit-container"> | 75 | <div class="submit-container"> |
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 6c1239395..b37596399 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.html +++ b/client/src/app/+videos/+video-edit/video-update.component.html | |||
@@ -10,11 +10,12 @@ | |||
10 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" | 10 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" |
11 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" | 11 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" |
12 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" | 12 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" |
13 | type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" | ||
13 | ></my-video-edit> | 14 | ></my-video-edit> |
14 | 15 | ||
15 | <div class="submit-container"> | 16 | <div class="submit-container"> |
16 | <my-button className="orange-button" i18n-label label="Update" icon="circle-tick" | 17 | <my-button className="orange-button" i18n-label label="Update" icon="circle-tick" |
17 | (click)="update()" (keydown.enter)="update()" | 18 | (click)="update()" (keydown.enter)="update()" |
18 | [disabled]="!form.valid || isUpdatingVideo === true" | 19 | [disabled]="!form.valid || isUpdatingVideo === true" |
19 | ></my-button> | 20 | ></my-button> |
20 | </div> | 21 | </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 2e1d0f89d..20438a2d3 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.ts +++ b/client/src/app/+videos/+video-edit/video-update.component.ts | |||
@@ -126,6 +126,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
126 | ) | 126 | ) |
127 | } | 127 | } |
128 | 128 | ||
129 | hydratePluginFieldsFromVideo () { | ||
130 | if (!this.video.pluginData) return | ||
131 | |||
132 | this.form.patchValue({ | ||
133 | pluginData: this.video.pluginData | ||
134 | }) | ||
135 | } | ||
136 | |||
129 | private hydrateFormFromVideo () { | 137 | private hydrateFormFromVideo () { |
130 | this.form.patchValue(this.video.toFormPatch()) | 138 | this.form.patchValue(this.video.toFormPatch()) |
131 | 139 | ||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts index fa20ec3b9..c1d0032cc 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts | |||
@@ -2,7 +2,8 @@ import { Observable } from 'rxjs' | |||
2 | import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core' | 2 | import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core' |
3 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
4 | import { Notifier, User } from '@app/core' | 4 | import { Notifier, User } from '@app/core' |
5 | import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms' | 5 | import { VIDEO_COMMENT_TEXT_VALIDATOR } from '@app/shared/form-validators/video-comment-validators' |
6 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
6 | import { Video } from '@app/shared/shared-main' | 7 | import { Video } from '@app/shared/shared-main' |
7 | import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment' | 8 | import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment' |
8 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 9 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
@@ -33,7 +34,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, | |||
33 | 34 | ||
34 | constructor ( | 35 | constructor ( |
35 | protected formValidatorService: FormValidatorService, | 36 | protected formValidatorService: FormValidatorService, |
36 | private videoCommentValidatorsService: VideoCommentValidatorsService, | ||
37 | private notifier: Notifier, | 37 | private notifier: Notifier, |
38 | private videoCommentService: VideoCommentService, | 38 | private videoCommentService: VideoCommentService, |
39 | private modalService: NgbModal, | 39 | private modalService: NgbModal, |
@@ -50,7 +50,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, | |||
50 | 50 | ||
51 | ngOnInit () { | 51 | ngOnInit () { |
52 | this.buildForm({ | 52 | this.buildForm({ |
53 | text: this.videoCommentValidatorsService.VIDEO_COMMENT_TEXT | 53 | text: VIDEO_COMMENT_TEXT_VALIDATOR |
54 | }) | 54 | }) |
55 | 55 | ||
56 | if (this.user) { | 56 | if (this.user) { |
diff --git a/client/src/app/+videos/+video-watch/video-watch-playlist.component.html b/client/src/app/+videos/+video-watch/video-watch-playlist.component.html index 246ef83cf..c270142a3 100644 --- a/client/src/app/+videos/+video-watch/video-watch-playlist.component.html +++ b/client/src/app/+videos/+video-watch/video-watch-playlist.component.html | |||
@@ -1,4 +1,7 @@ | |||
1 | <div *ngIf="playlist && video" class="playlist" myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()"> | 1 | <div |
2 | *ngIf="playlist && currentPlaylistPosition" class="playlist" | ||
3 | myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()" | ||
4 | > | ||
2 | <div class="playlist-info"> | 5 | <div class="playlist-info"> |
3 | <div class="playlist-display-name"> | 6 | <div class="playlist-display-name"> |
4 | {{ playlist.displayName }} | 7 | {{ playlist.displayName }} |
@@ -36,7 +39,7 @@ | |||
36 | </div> | 39 | </div> |
37 | </div> | 40 | </div> |
38 | 41 | ||
39 | <div *ngFor="let playlistElement of playlistElements"> | 42 | <div *ngFor="let playlistElement of playlistElements" [ngClass]="'element-' + playlistElement.position"> |
40 | <my-video-playlist-element-miniature | 43 | <my-video-playlist-element-miniature |
41 | [playlistElement]="playlistElement" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)" | 44 | [playlistElement]="playlistElement" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)" |
42 | [playing]="currentPlaylistPosition === playlistElement.position" [accountLink]="false" [position]="playlistElement.position" | 45 | [playing]="currentPlaylistPosition === playlistElement.position" [accountLink]="false" [position]="playlistElement.position" |
diff --git a/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts index c60ca4671..d76d0bbd2 100644 --- a/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | |
2 | import { Component, EventEmitter, Input, Output } from '@angular/core' | ||
2 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
3 | import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core' | 4 | import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core' |
4 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' | 5 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' |
5 | import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' | 6 | import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' |
6 | import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models' | 7 | import { VideoPlaylistPrivacy } from '@shared/models' |
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-video-watch-playlist', | 10 | selector: 'my-video-watch-playlist', |
@@ -14,9 +15,10 @@ export class VideoWatchPlaylistComponent { | |||
14 | static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' | 15 | static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' |
15 | static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist' | 16 | static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist' |
16 | 17 | ||
17 | @Input() video: VideoDetails | ||
18 | @Input() playlist: VideoPlaylist | 18 | @Input() playlist: VideoPlaylist |
19 | 19 | ||
20 | @Output() videoFound = new EventEmitter<string>() | ||
21 | |||
20 | playlistElements: VideoPlaylistElement[] = [] | 22 | playlistElements: VideoPlaylistElement[] = [] |
21 | playlistPagination: ComponentPagination = { | 23 | playlistPagination: ComponentPagination = { |
22 | currentPage: 1, | 24 | currentPage: 1, |
@@ -29,7 +31,8 @@ export class VideoWatchPlaylistComponent { | |||
29 | loopPlaylist: boolean | 31 | loopPlaylist: boolean |
30 | loopPlaylistSwitchText = '' | 32 | loopPlaylistSwitchText = '' |
31 | noPlaylistVideos = false | 33 | noPlaylistVideos = false |
32 | currentPlaylistPosition = 1 | 34 | |
35 | currentPlaylistPosition: number | ||
33 | 36 | ||
34 | constructor ( | 37 | constructor ( |
35 | private userService: UserService, | 38 | private userService: UserService, |
@@ -44,6 +47,7 @@ export class VideoWatchPlaylistComponent { | |||
44 | this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() | 47 | this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() |
45 | ? this.auth.getUser().autoPlayNextVideoPlaylist | 48 | ? this.auth.getUser().autoPlayNextVideoPlaylist |
46 | : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' | 49 | : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' |
50 | |||
47 | this.setAutoPlayNextVideoPlaylistSwitchText() | 51 | this.setAutoPlayNextVideoPlaylistSwitchText() |
48 | 52 | ||
49 | // defaults to false | 53 | // defaults to false |
@@ -51,12 +55,12 @@ export class VideoWatchPlaylistComponent { | |||
51 | this.setLoopPlaylistSwitchText() | 55 | this.setLoopPlaylistSwitchText() |
52 | } | 56 | } |
53 | 57 | ||
54 | onPlaylistVideosNearOfBottom () { | 58 | onPlaylistVideosNearOfBottom (position?: number) { |
55 | // Last page | 59 | // Last page |
56 | if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return | 60 | if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return |
57 | 61 | ||
58 | this.playlistPagination.currentPage += 1 | 62 | this.playlistPagination.currentPage += 1 |
59 | this.loadPlaylistElements(this.playlist,false) | 63 | this.loadPlaylistElements(this.playlist, false, position) |
60 | } | 64 | } |
61 | 65 | ||
62 | onElementRemoved (playlistElement: VideoPlaylistElement) { | 66 | onElementRemoved (playlistElement: VideoPlaylistElement) { |
@@ -83,26 +87,26 @@ export class VideoWatchPlaylistComponent { | |||
83 | return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC | 87 | return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC |
84 | } | 88 | } |
85 | 89 | ||
86 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) { | 90 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false, position?: number) { |
87 | this.videoPlaylist.getPlaylistVideos(playlist.uuid, this.playlistPagination) | 91 | this.videoPlaylist.getPlaylistVideos(playlist.uuid, this.playlistPagination) |
88 | .subscribe(({ total, data }) => { | 92 | .subscribe(({ total, data }) => { |
89 | this.playlistElements = this.playlistElements.concat(data) | 93 | this.playlistElements = this.playlistElements.concat(data) |
90 | this.playlistPagination.totalItems = total | 94 | this.playlistPagination.totalItems = total |
91 | 95 | ||
92 | const firstAvailableVideos = this.playlistElements.find(e => !!e.video) | 96 | const firstAvailableVideo = this.playlistElements.find(e => !!e.video) |
93 | if (!firstAvailableVideos) { | 97 | if (!firstAvailableVideo) { |
94 | this.noPlaylistVideos = true | 98 | this.noPlaylistVideos = true |
95 | return | 99 | return |
96 | } | 100 | } |
97 | 101 | ||
98 | this.updatePlaylistIndex(this.video) | 102 | if (position) this.updatePlaylistIndex(position) |
99 | 103 | ||
100 | if (redirectToFirst) { | 104 | if (redirectToFirst) { |
101 | const extras = { | 105 | const extras = { |
102 | queryParams: { | 106 | queryParams: { |
103 | start: firstAvailableVideos.startTimestamp, | 107 | start: firstAvailableVideo.startTimestamp, |
104 | stop: firstAvailableVideos.stopTimestamp, | 108 | stop: firstAvailableVideo.stopTimestamp, |
105 | videoId: firstAvailableVideos.video.uuid | 109 | playlistPosition: firstAvailableVideo.position |
106 | }, | 110 | }, |
107 | replaceUrl: true | 111 | replaceUrl: true |
108 | } | 112 | } |
@@ -111,18 +115,26 @@ export class VideoWatchPlaylistComponent { | |||
111 | }) | 115 | }) |
112 | } | 116 | } |
113 | 117 | ||
114 | updatePlaylistIndex (video: VideoDetails) { | 118 | updatePlaylistIndex (position: number) { |
115 | if (this.playlistElements.length === 0 || !video) return | 119 | if (this.playlistElements.length === 0 || !position) return |
116 | 120 | ||
117 | for (const playlistElement of this.playlistElements) { | 121 | for (const playlistElement of this.playlistElements) { |
118 | if (playlistElement.video && playlistElement.video.id === video.id) { | 122 | // >= if the previous videos were not valid |
123 | if (playlistElement.video && playlistElement.position >= position) { | ||
119 | this.currentPlaylistPosition = playlistElement.position | 124 | this.currentPlaylistPosition = playlistElement.position |
125 | |||
126 | this.videoFound.emit(playlistElement.video.uuid) | ||
127 | |||
128 | setTimeout(() => { | ||
129 | document.querySelector('.element-' + this.currentPlaylistPosition).scrollIntoView(false) | ||
130 | }, 0) | ||
131 | |||
120 | return | 132 | return |
121 | } | 133 | } |
122 | } | 134 | } |
123 | 135 | ||
124 | // Load more videos to find our video | 136 | // Load more videos to find our video |
125 | this.onPlaylistVideosNearOfBottom() | 137 | this.onPlaylistVideosNearOfBottom(position) |
126 | } | 138 | } |
127 | 139 | ||
128 | findNextPlaylistVideo (position = this.currentPlaylistPosition): VideoPlaylistElement { | 140 | findNextPlaylistVideo (position = this.currentPlaylistPosition): VideoPlaylistElement { |
@@ -147,9 +159,10 @@ export class VideoWatchPlaylistComponent { | |||
147 | navigateToNextPlaylistVideo () { | 159 | navigateToNextPlaylistVideo () { |
148 | const next = this.findNextPlaylistVideo(this.currentPlaylistPosition + 1) | 160 | const next = this.findNextPlaylistVideo(this.currentPlaylistPosition + 1) |
149 | if (!next) return | 161 | if (!next) return |
162 | |||
150 | const start = next.startTimestamp | 163 | const start = next.startTimestamp |
151 | const stop = next.stopTimestamp | 164 | const stop = next.stopTimestamp |
152 | this.router.navigate([],{ queryParams: { videoId: next.video.uuid, start, stop } }) | 165 | this.router.navigate([],{ queryParams: { playlistPosition: next.position, start, stop } }) |
153 | } | 166 | } |
154 | 167 | ||
155 | switchAutoPlayNextVideoPlaylist () { | 168 | switchAutoPlayNextVideoPlaylist () { |
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 4279437d2..076c6205f 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.html +++ b/client/src/app/+videos/+video-watch/video-watch.component.html | |||
@@ -11,7 +11,8 @@ | |||
11 | 11 | ||
12 | <my-video-watch-playlist | 12 | <my-video-watch-playlist |
13 | #videoWatchPlaylist | 13 | #videoWatchPlaylist |
14 | [video]="video" [playlist]="playlist" class="playlist" | 14 | [playlist]="playlist" class="playlist" |
15 | (videoFound)="onPlaylistVideoFound($event)" | ||
15 | ></my-video-watch-playlist> | 16 | ></my-video-watch-playlist> |
16 | </div> | 17 | </div> |
17 | 18 | ||
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 fb89bf6cd..1b2820810 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts | |||
@@ -53,6 +53,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
53 | video: VideoDetails = null | 53 | video: VideoDetails = null |
54 | videoCaptions: VideoCaption[] = [] | 54 | videoCaptions: VideoCaption[] = [] |
55 | 55 | ||
56 | playlistPosition: number | ||
56 | playlist: VideoPlaylist = null | 57 | playlist: VideoPlaylist = null |
57 | 58 | ||
58 | completeDescriptionShown = false | 59 | completeDescriptionShown = false |
@@ -140,9 +141,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
140 | if (playlistId) this.loadPlaylist(playlistId) | 141 | if (playlistId) this.loadPlaylist(playlistId) |
141 | }) | 142 | }) |
142 | 143 | ||
143 | this.queryParamsSub = this.route.queryParams.subscribe(async queryParams => { | 144 | this.queryParamsSub = this.route.queryParams.subscribe(queryParams => { |
144 | const videoId = queryParams[ 'videoId' ] | 145 | this.playlistPosition = queryParams[ 'playlistPosition' ] |
145 | if (videoId) this.loadVideo(videoId) | 146 | this.videoWatchPlaylist.updatePlaylistIndex(this.playlistPosition) |
146 | 147 | ||
147 | const start = queryParams[ 'start' ] | 148 | const start = queryParams[ 'start' ] |
148 | if (this.player && start) this.player.currentTime(parseInt(start, 10)) | 149 | if (this.player && start) this.player.currentTime(parseInt(start, 10)) |
@@ -335,6 +336,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
335 | return genericChannelDisplayName.includes(this.video.channel.displayName) | 336 | return genericChannelDisplayName.includes(this.video.channel.displayName) |
336 | } | 337 | } |
337 | 338 | ||
339 | onPlaylistVideoFound (videoId: string) { | ||
340 | this.loadVideo(videoId) | ||
341 | } | ||
342 | |||
338 | private loadVideo (videoId: string) { | 343 | private loadVideo (videoId: string) { |
339 | // Video did not change | 344 | // Video did not change |
340 | if (this.video && this.video.uuid === videoId) return | 345 | if (this.video && this.video.uuid === videoId) return |
@@ -362,6 +367,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
362 | const queryParams = this.route.snapshot.queryParams | 367 | const queryParams = this.route.snapshot.queryParams |
363 | 368 | ||
364 | const urlOptions = { | 369 | const urlOptions = { |
370 | resume: queryParams.resume, | ||
371 | |||
365 | startTime: queryParams.start, | 372 | startTime: queryParams.start, |
366 | stopTime: queryParams.stop, | 373 | stopTime: queryParams.stop, |
367 | 374 | ||
@@ -390,8 +397,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
390 | .subscribe(playlist => { | 397 | .subscribe(playlist => { |
391 | this.playlist = playlist | 398 | this.playlist = playlist |
392 | 399 | ||
393 | const videoId = this.route.snapshot.queryParams['videoId'] | 400 | this.videoWatchPlaylist.loadPlaylistElements(playlist, !this.playlistPosition, this.playlistPosition) |
394 | this.videoWatchPlaylist.loadPlaylistElements(playlist, !videoId) | ||
395 | }) | 401 | }) |
396 | } | 402 | } |
397 | 403 | ||
@@ -456,8 +462,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
456 | this.remoteServerDown = false | 462 | this.remoteServerDown = false |
457 | this.currentTime = undefined | 463 | this.currentTime = undefined |
458 | 464 | ||
459 | this.videoWatchPlaylist.updatePlaylistIndex(video) | ||
460 | |||
461 | if (this.isVideoBlur(this.video)) { | 465 | if (this.isVideoBlur(this.video)) { |
462 | const res = await this.confirmService.confirm( | 466 | const res = await this.confirmService.confirm( |
463 | $localize`This video contains mature or explicit content. Are you sure you want to watch it?`, | 467 | $localize`This video contains mature or explicit content. Are you sure you want to watch it?`, |
@@ -544,7 +548,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
544 | this.zone.run(() => this.theaterEnabled = enabled) | 548 | this.zone.run(() => this.theaterEnabled = enabled) |
545 | }) | 549 | }) |
546 | 550 | ||
547 | this.hooks.runAction('action:video-watch.player.loaded', 'video-watch', { player: this.player }) | 551 | this.hooks.runAction('action:video-watch.player.loaded', 'video-watch', { player: this.player, videojs, video: this.video }) |
548 | }) | 552 | }) |
549 | 553 | ||
550 | this.setVideoDescriptionHTML() | 554 | this.setVideoDescriptionHTML() |
@@ -657,16 +661,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
657 | const byUrl = urlOptions.startTime !== undefined | 661 | const byUrl = urlOptions.startTime !== undefined |
658 | const byHistory = video.userHistory && (!this.playlist || urlOptions.resume !== undefined) | 662 | const byHistory = video.userHistory && (!this.playlist || urlOptions.resume !== undefined) |
659 | 663 | ||
660 | if (byUrl) { | 664 | if (byUrl) return timeToInt(urlOptions.startTime) |
661 | return timeToInt(urlOptions.startTime) | 665 | if (byHistory) return video.userHistory.currentTime |
662 | } else if (byHistory) { | 666 | |
663 | return video.userHistory.currentTime | 667 | return 0 |
664 | } else { | ||
665 | return 0 | ||
666 | } | ||
667 | } | 668 | } |
668 | 669 | ||
669 | let startTime = getStartTime() | 670 | let startTime = getStartTime() |
671 | |||
670 | // If we are at the end of the video, reset the timer | 672 | // If we are at the end of the video, reset the timer |
671 | if (video.duration - startTime <= 1) startTime = 0 | 673 | if (video.duration - startTime <= 1) startTime = 0 |
672 | 674 | ||
diff --git a/client/src/app/+videos/videos-routing.module.ts b/client/src/app/+videos/videos-routing.module.ts index e0e877fc6..cf5f0b2e8 100644 --- a/client/src/app/+videos/videos-routing.module.ts +++ b/client/src/app/+videos/videos-routing.module.ts | |||
@@ -20,7 +20,7 @@ const videosRoutes: Routes = [ | |||
20 | component: VideoOverviewComponent, | 20 | component: VideoOverviewComponent, |
21 | data: { | 21 | data: { |
22 | meta: { | 22 | meta: { |
23 | title: 'Discover videos' | 23 | title: $localize`Discover videos` |
24 | } | 24 | } |
25 | } | 25 | } |
26 | }, | 26 | }, |
@@ -29,7 +29,7 @@ const videosRoutes: Routes = [ | |||
29 | component: VideoTrendingComponent, | 29 | component: VideoTrendingComponent, |
30 | data: { | 30 | data: { |
31 | meta: { | 31 | meta: { |
32 | title: 'Trending videos' | 32 | title: $localize`Trending videos` |
33 | }, | 33 | }, |
34 | reuse: { | 34 | reuse: { |
35 | enabled: true, | 35 | enabled: true, |
@@ -42,7 +42,7 @@ const videosRoutes: Routes = [ | |||
42 | component: VideoMostLikedComponent, | 42 | component: VideoMostLikedComponent, |
43 | data: { | 43 | data: { |
44 | meta: { | 44 | meta: { |
45 | title: 'Most liked videos' | 45 | title: $localize`Most liked videos` |
46 | }, | 46 | }, |
47 | reuse: { | 47 | reuse: { |
48 | enabled: true, | 48 | enabled: true, |
@@ -55,7 +55,7 @@ const videosRoutes: Routes = [ | |||
55 | component: VideoRecentlyAddedComponent, | 55 | component: VideoRecentlyAddedComponent, |
56 | data: { | 56 | data: { |
57 | meta: { | 57 | meta: { |
58 | title: 'Recently added videos' | 58 | title: $localize`Recently added videos` |
59 | }, | 59 | }, |
60 | reuse: { | 60 | reuse: { |
61 | enabled: true, | 61 | enabled: true, |
@@ -68,7 +68,7 @@ const videosRoutes: Routes = [ | |||
68 | component: VideoUserSubscriptionsComponent, | 68 | component: VideoUserSubscriptionsComponent, |
69 | data: { | 69 | data: { |
70 | meta: { | 70 | meta: { |
71 | title: 'Subscriptions' | 71 | title: $localize`Subscriptions` |
72 | }, | 72 | }, |
73 | reuse: { | 73 | reuse: { |
74 | enabled: true, | 74 | enabled: true, |
@@ -81,7 +81,7 @@ const videosRoutes: Routes = [ | |||
81 | component: VideoLocalComponent, | 81 | component: VideoLocalComponent, |
82 | data: { | 82 | data: { |
83 | meta: { | 83 | meta: { |
84 | title: 'Local videos' | 84 | title: $localize`Local videos` |
85 | }, | 85 | }, |
86 | reuse: { | 86 | reuse: { |
87 | enabled: true, | 87 | enabled: true, |
@@ -94,7 +94,7 @@ const videosRoutes: Routes = [ | |||
94 | loadChildren: () => import('@app/+videos/+video-edit/video-add.module').then(m => m.VideoAddModule), | 94 | loadChildren: () => import('@app/+videos/+video-edit/video-add.module').then(m => m.VideoAddModule), |
95 | data: { | 95 | data: { |
96 | meta: { | 96 | meta: { |
97 | title: 'Upload a video' | 97 | title: $localize`Upload a video` |
98 | } | 98 | } |
99 | } | 99 | } |
100 | }, | 100 | }, |
@@ -103,7 +103,7 @@ const videosRoutes: Routes = [ | |||
103 | loadChildren: () => import('@app/+videos/+video-edit/video-update.module').then(m => m.VideoUpdateModule), | 103 | loadChildren: () => import('@app/+videos/+video-edit/video-update.module').then(m => m.VideoUpdateModule), |
104 | data: { | 104 | data: { |
105 | meta: { | 105 | meta: { |
106 | title: 'Edit a video' | 106 | title: $localize`Edit a video` |
107 | } | 107 | } |
108 | } | 108 | } |
109 | }, | 109 | }, |
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 5b0439e6b..edec3216e 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -180,7 +180,7 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
180 | 180 | ||
181 | eventsObs.pipe( | 181 | eventsObs.pipe( |
182 | filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), | 182 | filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), |
183 | filter(() => this.screenService.isInSmallView() || !!this.screenService.isInTouchScreen()) | 183 | filter(() => this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) |
184 | ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page | 184 | ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page |
185 | } | 185 | } |
186 | 186 | ||
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index 34efa24fc..f10b37e5a 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts | |||
@@ -25,11 +25,13 @@ export class AuthUser extends User implements ServerMyUserModel { | |||
25 | canSeeVideosLink = true | 25 | canSeeVideosLink = true |
26 | 26 | ||
27 | static load () { | 27 | static load () { |
28 | const userInfo = getUserInfoFromLocalStorage() | 28 | const tokens = Tokens.load() |
29 | if (!tokens) return null | ||
29 | 30 | ||
31 | const userInfo = getUserInfoFromLocalStorage() | ||
30 | if (!userInfo) return null | 32 | if (!userInfo) return null |
31 | 33 | ||
32 | return new AuthUser(userInfo, Tokens.load()) | 34 | return new AuthUser(userInfo, tokens) |
33 | } | 35 | } |
34 | 36 | ||
35 | static flush () { | 37 | static flush () { |
diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index 671ee3e4f..9c0433bca 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts | |||
@@ -28,15 +28,16 @@ export class MenuService { | |||
28 | setMenuDisplay (display: boolean) { | 28 | setMenuDisplay (display: boolean) { |
29 | this.isMenuDisplayed = display | 29 | this.isMenuDisplayed = display |
30 | 30 | ||
31 | if (!this.screenService.isInTouchScreen()) return | ||
32 | |||
31 | // On touch screens, lock body scroll and display content overlay when memu is opened | 33 | // On touch screens, lock body scroll and display content overlay when memu is opened |
32 | if (this.screenService.isInTouchScreen()) { | 34 | if (this.isMenuDisplayed) { |
33 | if (this.isMenuDisplayed) { | 35 | document.body.classList.add('menu-open') |
34 | document.body.classList.add('menu-open') | 36 | this.screenService.onFingerSwipe('left', () => { this.setMenuDisplay(false) }) |
35 | this.screenService.onFingerSwipe('left', () => { this.setMenuDisplay(false) }) | 37 | return |
36 | } else { | ||
37 | document.body.classList.remove('menu-open') | ||
38 | } | ||
39 | } | 38 | } |
39 | |||
40 | document.body.classList.remove('menu-open') | ||
40 | } | 41 | } |
41 | 42 | ||
42 | onResize () { | 43 | onResize () { |
@@ -45,9 +46,7 @@ export class MenuService { | |||
45 | 46 | ||
46 | private handleWindowResize () { | 47 | private handleWindowResize () { |
47 | // On touch screens, do not handle window resize event since opened menu is handled with a content overlay | 48 | // On touch screens, do not handle window resize event since opened menu is handled with a content overlay |
48 | if (this.screenService.isInTouchScreen()) { | 49 | if (this.screenService.isInTouchScreen()) return |
49 | return | ||
50 | } | ||
51 | 50 | ||
52 | fromEvent(window, 'resize') | 51 | fromEvent(window, 'resize') |
53 | .pipe(debounceTime(200)) | 52 | .pipe(debounceTime(200)) |
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index dc115c0e1..4e44a1865 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -7,38 +7,22 @@ import { Notifier } from '@app/core/notification' | |||
7 | import { MarkdownService } from '@app/core/renderer' | 7 | import { MarkdownService } from '@app/core/renderer' |
8 | import { RestExtractor } from '@app/core/rest' | 8 | import { RestExtractor } from '@app/core/rest' |
9 | import { ServerService } from '@app/core/server/server.service' | 9 | import { ServerService } from '@app/core/server/server.service' |
10 | import { getDevLocale, importModule, isOnDevLocale } from '@app/helpers' | 10 | import { getDevLocale, isOnDevLocale } from '@app/helpers' |
11 | import { CustomModalComponent } from '@app/modal/custom-modal.component' | 11 | import { CustomModalComponent } from '@app/modal/custom-modal.component' |
12 | import { FormFields, Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins' | ||
12 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' | 13 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' |
13 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
14 | import { | 14 | import { |
15 | ClientHook, | 15 | ClientHook, |
16 | ClientHookName, | 16 | ClientHookName, |
17 | clientHookObject, | ||
18 | ClientScript, | ||
19 | PluginClientScope, | 17 | PluginClientScope, |
20 | PluginTranslation, | 18 | PluginTranslation, |
21 | PluginType, | 19 | PluginType, |
22 | PublicServerSetting, | 20 | PublicServerSetting, |
23 | RegisterClientHookOptions, | ||
24 | ServerConfigPlugin | 21 | ServerConfigPlugin |
25 | } from '@shared/models' | 22 | } from '@shared/models' |
26 | import { environment } from '../../../environments/environment' | 23 | import { environment } from '../../../environments/environment' |
27 | import { ClientScript as ClientScriptModule } from '../../../types/client-script.model' | ||
28 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' | 24 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' |
29 | 25 | ||
30 | interface HookStructValue extends RegisterClientHookOptions { | ||
31 | plugin: ServerConfigPlugin | ||
32 | clientScript: ClientScript | ||
33 | } | ||
34 | |||
35 | type PluginInfo = { | ||
36 | plugin: ServerConfigPlugin | ||
37 | clientScript: ClientScript | ||
38 | pluginType: PluginType | ||
39 | isTheme: boolean | ||
40 | } | ||
41 | |||
42 | @Injectable() | 26 | @Injectable() |
43 | export class PluginService implements ClientHook { | 27 | export class PluginService implements ClientHook { |
44 | private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' | 28 | private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' |
@@ -51,7 +35,9 @@ export class PluginService implements ClientHook { | |||
51 | search: new ReplaySubject<boolean>(1), | 35 | search: new ReplaySubject<boolean>(1), |
52 | 'video-watch': new ReplaySubject<boolean>(1), | 36 | 'video-watch': new ReplaySubject<boolean>(1), |
53 | signup: new ReplaySubject<boolean>(1), | 37 | signup: new ReplaySubject<boolean>(1), |
54 | login: new ReplaySubject<boolean>(1) | 38 | login: new ReplaySubject<boolean>(1), |
39 | 'video-edit': new ReplaySubject<boolean>(1), | ||
40 | embed: new ReplaySubject<boolean>(1) | ||
55 | } | 41 | } |
56 | 42 | ||
57 | translationsObservable: Observable<PluginTranslation> | 43 | translationsObservable: Observable<PluginTranslation> |
@@ -64,7 +50,10 @@ export class PluginService implements ClientHook { | |||
64 | private loadedScopes: PluginClientScope[] = [] | 50 | private loadedScopes: PluginClientScope[] = [] |
65 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} | 51 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} |
66 | 52 | ||
67 | private hooks: { [ name: string ]: HookStructValue[] } = {} | 53 | private hooks: Hooks = {} |
54 | private formFields: FormFields = { | ||
55 | video: [] | ||
56 | } | ||
68 | 57 | ||
69 | constructor ( | 58 | constructor ( |
70 | private authService: AuthService, | 59 | private authService: AuthService, |
@@ -120,7 +109,7 @@ export class PluginService implements ClientHook { | |||
120 | this.scopes[scope].push({ | 109 | this.scopes[scope].push({ |
121 | plugin, | 110 | plugin, |
122 | clientScript: { | 111 | clientScript: { |
123 | script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, | 112 | script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, |
124 | scopes: clientScript.scopes | 113 | scopes: clientScript.scopes |
125 | }, | 114 | }, |
126 | pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, | 115 | pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, |
@@ -184,20 +173,8 @@ export class PluginService implements ClientHook { | |||
184 | } | 173 | } |
185 | 174 | ||
186 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { | 175 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { |
187 | return this.zone.runOutsideAngular(async () => { | 176 | return this.zone.runOutsideAngular(() => { |
188 | if (!this.hooks[ hookName ]) return result | 177 | return runHook(this.hooks, hookName, result, params) |
189 | |||
190 | const hookType = getHookType(hookName) | ||
191 | |||
192 | for (const hook of this.hooks[ hookName ]) { | ||
193 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) | ||
194 | |||
195 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | ||
196 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) | ||
197 | }) | ||
198 | } | ||
199 | |||
200 | return result | ||
201 | }) | 178 | }) |
202 | } | 179 | } |
203 | 180 | ||
@@ -215,35 +192,18 @@ export class PluginService implements ClientHook { | |||
215 | : PluginType.THEME | 192 | : PluginType.THEME |
216 | } | 193 | } |
217 | 194 | ||
218 | private loadPlugin (pluginInfo: PluginInfo) { | 195 | getRegisteredVideoFormFields (type: 'import-url' | 'import-torrent' | 'upload' | 'update') { |
219 | const { plugin, clientScript } = pluginInfo | 196 | return this.formFields.video.filter(f => f.videoFormOptions.type === type) |
220 | 197 | } | |
221 | const registerHook = (options: RegisterClientHookOptions) => { | ||
222 | if (clientHookObject[options.target] !== true) { | ||
223 | console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name) | ||
224 | return | ||
225 | } | ||
226 | |||
227 | if (!this.hooks[options.target]) this.hooks[options.target] = [] | ||
228 | |||
229 | this.hooks[options.target].push({ | ||
230 | plugin, | ||
231 | clientScript, | ||
232 | target: options.target, | ||
233 | handler: options.handler, | ||
234 | priority: options.priority || 0 | ||
235 | }) | ||
236 | } | ||
237 | |||
238 | const peertubeHelpers = this.buildPeerTubeHelpers(pluginInfo) | ||
239 | |||
240 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | ||
241 | 198 | ||
199 | private loadPlugin (pluginInfo: PluginInfo) { | ||
242 | return this.zone.runOutsideAngular(() => { | 200 | return this.zone.runOutsideAngular(() => { |
243 | return importModule(clientScript.script) | 201 | return loadPlugin({ |
244 | .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) | 202 | hooks: this.hooks, |
245 | .then(() => this.sortHooksByPriority()) | 203 | formFields: this.formFields, |
246 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | 204 | pluginInfo, |
205 | peertubeHelpersFactory: pluginInfo => this.buildPeerTubeHelpers(pluginInfo) | ||
206 | }) | ||
247 | }) | 207 | }) |
248 | } | 208 | } |
249 | 209 | ||
@@ -253,14 +213,6 @@ export class PluginService implements ClientHook { | |||
253 | } | 213 | } |
254 | } | 214 | } |
255 | 215 | ||
256 | private sortHooksByPriority () { | ||
257 | for (const hookName of Object.keys(this.hooks)) { | ||
258 | this.hooks[hookName].sort((a, b) => { | ||
259 | return b.priority - a.priority | ||
260 | }) | ||
261 | } | ||
262 | } | ||
263 | |||
264 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { | 216 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { |
265 | const { plugin } = pluginInfo | 217 | const { plugin } = pluginInfo |
266 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 218 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
diff --git a/client/src/app/core/wrappers/screen.service.ts b/client/src/app/core/wrappers/screen.service.ts index 88cf662b3..a085e5bdc 100644 --- a/client/src/app/core/wrappers/screen.service.ts +++ b/client/src/app/core/wrappers/screen.service.ts | |||
@@ -30,7 +30,7 @@ export class ScreenService { | |||
30 | } | 30 | } |
31 | 31 | ||
32 | isInTouchScreen () { | 32 | isInTouchScreen () { |
33 | return 'ontouchstart' in window || navigator.msMaxTouchPoints | 33 | return !!('ontouchstart' in window || navigator.msMaxTouchPoints) |
34 | } | 34 | } |
35 | 35 | ||
36 | getNumberOfAvailableMiniatures () { | 36 | getNumberOfAvailableMiniatures () { |
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts index d05541ca9..d9007dd77 100644 --- a/client/src/app/helpers/utils.ts +++ b/client/src/app/helpers/utils.ts | |||
@@ -148,41 +148,6 @@ function scrollToTop () { | |||
148 | window.scroll(0, 0) | 148 | window.scroll(0, 0) |
149 | } | 149 | } |
150 | 150 | ||
151 | // Thanks: https://github.com/uupaa/dynamic-import-polyfill | ||
152 | function importModule (path: string) { | ||
153 | return new Promise((resolve, reject) => { | ||
154 | const vector = '$importModule$' + Math.random().toString(32).slice(2) | ||
155 | const script = document.createElement('script') | ||
156 | |||
157 | const destructor = () => { | ||
158 | delete window[ vector ] | ||
159 | script.onerror = null | ||
160 | script.onload = null | ||
161 | script.remove() | ||
162 | URL.revokeObjectURL(script.src) | ||
163 | script.src = '' | ||
164 | } | ||
165 | |||
166 | script.defer = true | ||
167 | script.type = 'module' | ||
168 | |||
169 | script.onerror = () => { | ||
170 | reject(new Error(`Failed to import: ${path}`)) | ||
171 | destructor() | ||
172 | } | ||
173 | script.onload = () => { | ||
174 | resolve(window[ vector ]) | ||
175 | destructor() | ||
176 | } | ||
177 | const absURL = (environment.apiUrl || window.location.origin) + path | ||
178 | const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module | ||
179 | const blob = new Blob([ loader ], { type: 'text/javascript' }) | ||
180 | script.src = URL.createObjectURL(blob) | ||
181 | |||
182 | document.head.appendChild(script) | ||
183 | }) | ||
184 | } | ||
185 | |||
186 | function isInViewport (el: HTMLElement) { | 151 | function isInViewport (el: HTMLElement) { |
187 | const bounding = el.getBoundingClientRect() | 152 | const bounding = el.getBoundingClientRect() |
188 | return ( | 153 | return ( |
@@ -216,7 +181,6 @@ export { | |||
216 | getAbsoluteEmbedUrl, | 181 | getAbsoluteEmbedUrl, |
217 | objectLineFeedToHtml, | 182 | objectLineFeedToHtml, |
218 | removeElementFromArray, | 183 | removeElementFromArray, |
219 | importModule, | ||
220 | scrollToTop, | 184 | scrollToTop, |
221 | isInViewport, | 185 | isInViewport, |
222 | isXPercentInViewport | 186 | isXPercentInViewport |
diff --git a/client/src/app/shared/form-validators/abuse-validators.ts b/client/src/app/shared/form-validators/abuse-validators.ts new file mode 100644 index 000000000..75bfacf01 --- /dev/null +++ b/client/src/app/shared/form-validators/abuse-validators.ts | |||
@@ -0,0 +1,29 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const ABUSE_REASON_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [Validators.required, Validators.minLength(2), Validators.maxLength(3000)], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`Report reason is required.`, | ||
8 | 'minlength': $localize`Report reason must be at least 2 characters long.`, | ||
9 | 'maxlength': $localize`Report reason cannot be more than 3000 characters long.` | ||
10 | } | ||
11 | } | ||
12 | |||
13 | export const ABUSE_MODERATION_COMMENT_VALIDATOR: BuildFormValidator = { | ||
14 | VALIDATORS: [Validators.required, Validators.minLength(2), Validators.maxLength(3000)], | ||
15 | MESSAGES: { | ||
16 | 'required': $localize`Moderation comment is required.`, | ||
17 | 'minlength': $localize`Moderation comment must be at least 2 characters long.`, | ||
18 | 'maxlength': $localize`Moderation comment cannot be more than 3000 characters long.` | ||
19 | } | ||
20 | } | ||
21 | |||
22 | export const ABUSE_MESSAGE_VALIDATOR: BuildFormValidator = { | ||
23 | VALIDATORS: [Validators.required, Validators.minLength(2), Validators.maxLength(3000)], | ||
24 | MESSAGES: { | ||
25 | 'required': $localize`Abuse message is required.`, | ||
26 | 'minlength': $localize`Abuse message must be at least 2 characters long.`, | ||
27 | 'maxlength': $localize`Abuse message cannot be more than 3000 characters long.` | ||
28 | } | ||
29 | } | ||
diff --git a/client/src/app/shared/form-validators/batch-domains-validators.ts b/client/src/app/shared/form-validators/batch-domains-validators.ts new file mode 100644 index 000000000..423d1337f --- /dev/null +++ b/client/src/app/shared/form-validators/batch-domains-validators.ts | |||
@@ -0,0 +1,60 @@ | |||
1 | import { AbstractControl, FormControl, ValidatorFn, Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | import { validateHost } from './host' | ||
4 | |||
5 | export function getNotEmptyHosts (hosts: string) { | ||
6 | return hosts | ||
7 | .split('\n') | ||
8 | .filter((host: string) => host && host.length !== 0) // Eject empty hosts | ||
9 | } | ||
10 | |||
11 | const validDomains: ValidatorFn = (control: FormControl) => { | ||
12 | if (!control.value) return null | ||
13 | |||
14 | const newHostsErrors = [] | ||
15 | const hosts = getNotEmptyHosts(control.value) | ||
16 | |||
17 | for (const host of hosts) { | ||
18 | if (validateHost(host) === false) { | ||
19 | newHostsErrors.push($localize`${host} is not valid`) | ||
20 | } | ||
21 | } | ||
22 | |||
23 | /* Is not valid. */ | ||
24 | if (newHostsErrors.length !== 0) { | ||
25 | return { | ||
26 | 'validDomains': { | ||
27 | reason: 'invalid', | ||
28 | value: newHostsErrors.join('. ') + '.' | ||
29 | } | ||
30 | } | ||
31 | } | ||
32 | |||
33 | /* Is valid. */ | ||
34 | return null | ||
35 | } | ||
36 | |||
37 | const isHostsUnique: ValidatorFn = (control: AbstractControl) => { | ||
38 | if (!control.value) return null | ||
39 | |||
40 | const hosts = getNotEmptyHosts(control.value) | ||
41 | |||
42 | if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) { | ||
43 | return null | ||
44 | } else { | ||
45 | return { | ||
46 | 'uniqueDomains': { | ||
47 | reason: 'invalid' | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | |||
53 | export const DOMAINS_VALIDATOR: BuildFormValidator = { | ||
54 | VALIDATORS: [Validators.required, validDomains, isHostsUnique], | ||
55 | MESSAGES: { | ||
56 | 'required': $localize`Domain is required.`, | ||
57 | 'validDomains': $localize`Domains entered are invalid.`, | ||
58 | 'uniqueDomains': $localize`Domains entered contain duplicates.` | ||
59 | } | ||
60 | } | ||
diff --git a/client/src/app/shared/form-validators/custom-config-validators.ts b/client/src/app/shared/form-validators/custom-config-validators.ts new file mode 100644 index 000000000..41b3cbba9 --- /dev/null +++ b/client/src/app/shared/form-validators/custom-config-validators.ts | |||
@@ -0,0 +1,80 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const INSTANCE_NAME_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [Validators.required], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`Instance name is required.` | ||
8 | } | ||
9 | } | ||
10 | |||
11 | export const INSTANCE_SHORT_DESCRIPTION_VALIDATOR: BuildFormValidator = { | ||
12 | VALIDATORS: [Validators.max(250)], | ||
13 | MESSAGES: { | ||
14 | 'max': $localize`Short description should not be longer than 250 characters.` | ||
15 | } | ||
16 | } | ||
17 | |||
18 | export const SERVICES_TWITTER_USERNAME_VALIDATOR: BuildFormValidator = { | ||
19 | VALIDATORS: [Validators.required], | ||
20 | MESSAGES: { | ||
21 | 'required': $localize`Twitter username is required.` | ||
22 | } | ||
23 | } | ||
24 | |||
25 | export const CACHE_PREVIEWS_SIZE_VALIDATOR: BuildFormValidator = { | ||
26 | VALIDATORS: [Validators.required, Validators.min(1), Validators.pattern('[0-9]+')], | ||
27 | MESSAGES: { | ||
28 | 'required': $localize`Previews cache size is required.`, | ||
29 | 'min': $localize`Previews cache size must be greater than 1.`, | ||
30 | 'pattern': $localize`Previews cache size must be a number.` | ||
31 | } | ||
32 | } | ||
33 | |||
34 | export const CACHE_CAPTIONS_SIZE_VALIDATOR: BuildFormValidator = { | ||
35 | VALIDATORS: [Validators.required, Validators.min(1), Validators.pattern('[0-9]+')], | ||
36 | MESSAGES: { | ||
37 | 'required': $localize`Captions cache size is required.`, | ||
38 | 'min': $localize`Captions cache size must be greater than 1.`, | ||
39 | 'pattern': $localize`Captions cache size must be a number.` | ||
40 | } | ||
41 | } | ||
42 | |||
43 | export const SIGNUP_LIMIT_VALIDATOR: BuildFormValidator = { | ||
44 | VALIDATORS: [Validators.required, Validators.min(-1), Validators.pattern('-?[0-9]+')], | ||
45 | MESSAGES: { | ||
46 | 'required': $localize`Signup limit is required.`, | ||
47 | 'min': $localize`Signup limit must be greater than 1.`, | ||
48 | 'pattern': $localize`Signup limit must be a number.` | ||
49 | } | ||
50 | } | ||
51 | |||
52 | export const ADMIN_EMAIL_VALIDATOR: BuildFormValidator = { | ||
53 | VALIDATORS: [Validators.required, Validators.email], | ||
54 | MESSAGES: { | ||
55 | 'required': $localize`Admin email is required.`, | ||
56 | 'email': $localize`Admin email must be valid.` | ||
57 | } | ||
58 | } | ||
59 | |||
60 | export const TRANSCODING_THREADS_VALIDATOR: BuildFormValidator = { | ||
61 | VALIDATORS: [Validators.required, Validators.min(0)], | ||
62 | MESSAGES: { | ||
63 | 'required': $localize`Transcoding threads is required.`, | ||
64 | 'min': $localize`Transcoding threads must be greater or equal to 0.` | ||
65 | } | ||
66 | } | ||
67 | |||
68 | export const INDEX_URL_VALIDATOR: BuildFormValidator = { | ||
69 | VALIDATORS: [Validators.pattern(/^https:\/\//)], | ||
70 | MESSAGES: { | ||
71 | 'pattern': $localize`Index URL should be a URL` | ||
72 | } | ||
73 | } | ||
74 | |||
75 | export const SEARCH_INDEX_URL_VALIDATOR: BuildFormValidator = { | ||
76 | VALIDATORS: [Validators.pattern(/^https?:\/\//)], | ||
77 | MESSAGES: { | ||
78 | 'pattern': $localize`Search index URL should be a URL` | ||
79 | } | ||
80 | } | ||
diff --git a/client/src/app/shared/form-validators/form-validator.model.ts b/client/src/app/shared/form-validators/form-validator.model.ts new file mode 100644 index 000000000..248a3b1d3 --- /dev/null +++ b/client/src/app/shared/form-validators/form-validator.model.ts | |||
@@ -0,0 +1,14 @@ | |||
1 | import { ValidatorFn } from '@angular/forms' | ||
2 | |||
3 | export type BuildFormValidator = { | ||
4 | VALIDATORS: ValidatorFn[], | ||
5 | MESSAGES: { [ name: string ]: string } | ||
6 | } | ||
7 | |||
8 | export type BuildFormArgument = { | ||
9 | [ id: string ]: BuildFormValidator | BuildFormArgument | ||
10 | } | ||
11 | |||
12 | export type BuildFormDefaultValues = { | ||
13 | [ name: string ]: string | string[] | BuildFormDefaultValues | ||
14 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/host.ts b/client/src/app/shared/form-validators/host.ts index c18a35f9b..c18a35f9b 100644 --- a/client/src/app/shared/shared-forms/form-validators/host.ts +++ b/client/src/app/shared/form-validators/host.ts | |||
diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts new file mode 100644 index 000000000..f621f03a4 --- /dev/null +++ b/client/src/app/shared/form-validators/index.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | export * from './form-validator.model' | ||
2 | export * from './host' | ||
3 | |||
4 | // Don't re export const variables because webpack 4 cannot do tree shaking with them | ||
5 | // export * from './abuse-validators' | ||
6 | // export * from './batch-domains-validators' | ||
7 | // export * from './custom-config-validators' | ||
8 | // export * from './instance-validators' | ||
9 | // export * from './login-validators' | ||
10 | // export * from './reset-password-validators' | ||
11 | // export * from './user-validators' | ||
12 | // export * from './video-block-validators' | ||
13 | // export * from './video-captions-validators' | ||
14 | // export * from './video-channel-validators' | ||
15 | // export * from './video-comment-validators' | ||
16 | // export * from './video-playlist-validators' | ||
17 | // export * from './video-validators' | ||
diff --git a/client/src/app/shared/form-validators/instance-validators.ts b/client/src/app/shared/form-validators/instance-validators.ts new file mode 100644 index 000000000..a72e28ba0 --- /dev/null +++ b/client/src/app/shared/form-validators/instance-validators.ts | |||
@@ -0,0 +1,49 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const FROM_EMAIL_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [Validators.required, Validators.email], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`Email is required.`, | ||
8 | 'email': $localize`Email must be valid.` | ||
9 | } | ||
10 | } | ||
11 | |||
12 | export const FROM_NAME_VALIDATOR: BuildFormValidator = { | ||
13 | VALIDATORS: [ | ||
14 | Validators.required, | ||
15 | Validators.minLength(1), | ||
16 | Validators.maxLength(120) | ||
17 | ], | ||
18 | MESSAGES: { | ||
19 | 'required': $localize`Your name is required.`, | ||
20 | 'minlength': $localize`Your name must be at least 1 character long.`, | ||
21 | 'maxlength': $localize`Your name cannot be more than 120 characters long.` | ||
22 | } | ||
23 | } | ||
24 | |||
25 | export const SUBJECT_VALIDATOR: BuildFormValidator = { | ||
26 | VALIDATORS: [ | ||
27 | Validators.required, | ||
28 | Validators.minLength(1), | ||
29 | Validators.maxLength(120) | ||
30 | ], | ||
31 | MESSAGES: { | ||
32 | 'required': $localize`A subject is required.`, | ||
33 | 'minlength': $localize`The subject must be at least 1 character long.`, | ||
34 | 'maxlength': $localize`The subject cannot be more than 120 characters long.` | ||
35 | } | ||
36 | } | ||
37 | |||
38 | export const BODY_VALIDATOR: BuildFormValidator = { | ||
39 | VALIDATORS: [ | ||
40 | Validators.required, | ||
41 | Validators.minLength(3), | ||
42 | Validators.maxLength(5000) | ||
43 | ], | ||
44 | MESSAGES: { | ||
45 | 'required': $localize`A message is required.`, | ||
46 | 'minlength': $localize`The message must be at least 3 characters long.`, | ||
47 | 'maxlength': $localize`The message cannot be more than 5000 characters long.` | ||
48 | } | ||
49 | } | ||
diff --git a/client/src/app/shared/form-validators/login-validators.ts b/client/src/app/shared/form-validators/login-validators.ts new file mode 100644 index 000000000..1ceae1be3 --- /dev/null +++ b/client/src/app/shared/form-validators/login-validators.ts | |||
@@ -0,0 +1,20 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const LOGIN_USERNAME_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ | ||
6 | Validators.required | ||
7 | ], | ||
8 | MESSAGES: { | ||
9 | 'required': $localize`Username is required.` | ||
10 | } | ||
11 | } | ||
12 | |||
13 | export const LOGIN_PASSWORD_VALIDATOR: BuildFormValidator = { | ||
14 | VALIDATORS: [ | ||
15 | Validators.required | ||
16 | ], | ||
17 | MESSAGES: { | ||
18 | 'required': $localize`Password is required.` | ||
19 | } | ||
20 | } | ||
diff --git a/client/src/app/shared/form-validators/reset-password-validators.ts b/client/src/app/shared/form-validators/reset-password-validators.ts new file mode 100644 index 000000000..b87f2eab9 --- /dev/null +++ b/client/src/app/shared/form-validators/reset-password-validators.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const RESET_PASSWORD_CONFIRM_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ | ||
6 | Validators.required | ||
7 | ], | ||
8 | MESSAGES: { | ||
9 | 'required': $localize`Confirmation of the password is required.` | ||
10 | } | ||
11 | } | ||
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts new file mode 100644 index 000000000..18199505c --- /dev/null +++ b/client/src/app/shared/form-validators/user-validators.ts | |||
@@ -0,0 +1,144 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const USER_USERNAME_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ | ||
6 | Validators.required, | ||
7 | Validators.minLength(1), | ||
8 | Validators.maxLength(50), | ||
9 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | ||
10 | ], | ||
11 | MESSAGES: { | ||
12 | 'required': $localize`Username is required.`, | ||
13 | 'minlength': $localize`Username must be at least 1 character long.`, | ||
14 | 'maxlength': $localize`Username cannot be more than 50 characters long.`, | ||
15 | 'pattern': $localize`Username should be lowercase alphanumeric; dots and underscores are allowed.` | ||
16 | } | ||
17 | } | ||
18 | |||
19 | export const USER_CHANNEL_NAME_VALIDATOR: BuildFormValidator = { | ||
20 | VALIDATORS: [ | ||
21 | Validators.required, | ||
22 | Validators.minLength(1), | ||
23 | Validators.maxLength(50), | ||
24 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | ||
25 | ], | ||
26 | MESSAGES: { | ||
27 | 'required': $localize`Channel name is required.`, | ||
28 | 'minlength': $localize`Channel name must be at least 1 character long.`, | ||
29 | 'maxlength': $localize`Channel name cannot be more than 50 characters long.`, | ||
30 | 'pattern': $localize`Channel name should be lowercase alphanumeric; dots and underscores are allowed.` | ||
31 | } | ||
32 | } | ||
33 | |||
34 | export const USER_EMAIL_VALIDATOR: BuildFormValidator = { | ||
35 | VALIDATORS: [ Validators.required, Validators.email ], | ||
36 | MESSAGES: { | ||
37 | 'required': $localize`Email is required.`, | ||
38 | 'email': $localize`Email must be valid.` | ||
39 | } | ||
40 | } | ||
41 | |||
42 | export const USER_PASSWORD_VALIDATOR: BuildFormValidator = { | ||
43 | VALIDATORS: [ | ||
44 | Validators.required, | ||
45 | Validators.minLength(6), | ||
46 | Validators.maxLength(255) | ||
47 | ], | ||
48 | MESSAGES: { | ||
49 | 'required': $localize`Password is required.`, | ||
50 | 'minlength': $localize`Password must be at least 6 characters long.`, | ||
51 | 'maxlength': $localize`Password cannot be more than 255 characters long.` | ||
52 | } | ||
53 | } | ||
54 | |||
55 | export const USER_PASSWORD_OPTIONAL_VALIDATOR: BuildFormValidator = { | ||
56 | VALIDATORS: [ | ||
57 | Validators.minLength(6), | ||
58 | Validators.maxLength(255) | ||
59 | ], | ||
60 | MESSAGES: { | ||
61 | 'minlength': $localize`Password must be at least 6 characters long.`, | ||
62 | 'maxlength': $localize`Password cannot be more than 255 characters long.` | ||
63 | } | ||
64 | } | ||
65 | |||
66 | export const USER_CONFIRM_PASSWORD_VALIDATOR: BuildFormValidator = { | ||
67 | VALIDATORS: [], | ||
68 | MESSAGES: { | ||
69 | 'matchPassword': $localize`The new password and the confirmed password do not correspond.` | ||
70 | } | ||
71 | } | ||
72 | |||
73 | export const USER_VIDEO_QUOTA_VALIDATOR: BuildFormValidator = { | ||
74 | VALIDATORS: [ Validators.required, Validators.min(-1) ], | ||
75 | MESSAGES: { | ||
76 | 'required': $localize`Video quota is required.`, | ||
77 | 'min': $localize`Quota must be greater than -1.` | ||
78 | } | ||
79 | } | ||
80 | export const USER_VIDEO_QUOTA_DAILY_VALIDATOR: BuildFormValidator = { | ||
81 | VALIDATORS: [ Validators.required, Validators.min(-1) ], | ||
82 | MESSAGES: { | ||
83 | 'required': $localize`Daily upload limit is required.`, | ||
84 | 'min': $localize`Daily upload limit must be greater than -1.` | ||
85 | } | ||
86 | } | ||
87 | |||
88 | export const USER_ROLE_VALIDATOR: BuildFormValidator = { | ||
89 | VALIDATORS: [ Validators.required ], | ||
90 | MESSAGES: { | ||
91 | 'required': $localize`User role is required.` | ||
92 | } | ||
93 | } | ||
94 | |||
95 | export const USER_DISPLAY_NAME_REQUIRED_VALIDATOR = buildDisplayNameValidator(true) | ||
96 | |||
97 | export const USER_DESCRIPTION_VALIDATOR: BuildFormValidator = { | ||
98 | VALIDATORS: [ | ||
99 | Validators.minLength(3), | ||
100 | Validators.maxLength(1000) | ||
101 | ], | ||
102 | MESSAGES: { | ||
103 | 'minlength': $localize`Description must be at least 3 characters long.`, | ||
104 | 'maxlength': $localize`Description cannot be more than 1000 characters long.` | ||
105 | } | ||
106 | } | ||
107 | |||
108 | export const USER_TERMS_VALIDATOR: BuildFormValidator = { | ||
109 | VALIDATORS: [ | ||
110 | Validators.requiredTrue | ||
111 | ], | ||
112 | MESSAGES: { | ||
113 | 'required': $localize`You must agree with the instance terms in order to register on it.` | ||
114 | } | ||
115 | } | ||
116 | |||
117 | export const USER_BAN_REASON_VALIDATOR: BuildFormValidator = { | ||
118 | VALIDATORS: [ | ||
119 | Validators.minLength(3), | ||
120 | Validators.maxLength(250) | ||
121 | ], | ||
122 | MESSAGES: { | ||
123 | 'minlength': $localize`Ban reason must be at least 3 characters long.`, | ||
124 | 'maxlength': $localize`Ban reason cannot be more than 250 characters long.` | ||
125 | } | ||
126 | } | ||
127 | |||
128 | function buildDisplayNameValidator (required: boolean) { | ||
129 | const control = { | ||
130 | VALIDATORS: [ | ||
131 | Validators.minLength(1), | ||
132 | Validators.maxLength(120) | ||
133 | ], | ||
134 | MESSAGES: { | ||
135 | 'required': $localize`Display name is required.`, | ||
136 | 'minlength': $localize`Display name must be at least 1 character long.`, | ||
137 | 'maxlength': $localize`Display name cannot be more than 50 characters long.` | ||
138 | } | ||
139 | } | ||
140 | |||
141 | if (required) control.VALIDATORS.push(Validators.required) | ||
142 | |||
143 | return control | ||
144 | } | ||
diff --git a/client/src/app/shared/form-validators/video-block-validators.ts b/client/src/app/shared/form-validators/video-block-validators.ts new file mode 100644 index 000000000..d3974aefe --- /dev/null +++ b/client/src/app/shared/form-validators/video-block-validators.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const VIDEO_BLOCK_REASON_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ], | ||
6 | MESSAGES: { | ||
7 | 'minlength': $localize`Block reason must be at least 2 characters long.`, | ||
8 | 'maxlength': $localize`Block reason cannot be more than 300 characters long.` | ||
9 | } | ||
10 | } | ||
diff --git a/client/src/app/shared/form-validators/video-captions-validators.ts b/client/src/app/shared/form-validators/video-captions-validators.ts new file mode 100644 index 000000000..9742d2925 --- /dev/null +++ b/client/src/app/shared/form-validators/video-captions-validators.ts | |||
@@ -0,0 +1,16 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const VIDEO_CAPTION_LANGUAGE_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ Validators.required ], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`Video caption language is required.` | ||
8 | } | ||
9 | } | ||
10 | |||
11 | export const VIDEO_CAPTION_FILE_VALIDATOR: BuildFormValidator = { | ||
12 | VALIDATORS: [ Validators.required ], | ||
13 | MESSAGES: { | ||
14 | 'required': $localize`Video caption file is required.` | ||
15 | } | ||
16 | } | ||
diff --git a/client/src/app/shared/form-validators/video-channel-validators.ts b/client/src/app/shared/form-validators/video-channel-validators.ts new file mode 100644 index 000000000..0daab22ce --- /dev/null +++ b/client/src/app/shared/form-validators/video-channel-validators.ts | |||
@@ -0,0 +1,52 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const VIDEO_CHANNEL_NAME_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ | ||
6 | Validators.required, | ||
7 | Validators.minLength(1), | ||
8 | Validators.maxLength(50), | ||
9 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | ||
10 | ], | ||
11 | MESSAGES: { | ||
12 | 'required': $localize`Name is required.`, | ||
13 | 'minlength': $localize`Name must be at least 1 character long.`, | ||
14 | 'maxlength': $localize`Name cannot be more than 50 characters long.`, | ||
15 | 'pattern': $localize`Name should be lowercase alphanumeric; dots and underscores are allowed.` | ||
16 | } | ||
17 | } | ||
18 | |||
19 | export const VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR: BuildFormValidator = { | ||
20 | VALIDATORS: [ | ||
21 | Validators.required, | ||
22 | Validators.minLength(1), | ||
23 | Validators.maxLength(50) | ||
24 | ], | ||
25 | MESSAGES: { | ||
26 | 'required': $localize`Display name is required.`, | ||
27 | 'minlength': $localize`Display name must be at least 1 character long.`, | ||
28 | 'maxlength': $localize`Display name cannot be more than 50 characters long.` | ||
29 | } | ||
30 | } | ||
31 | |||
32 | export const VIDEO_CHANNEL_DESCRIPTION_VALIDATOR: BuildFormValidator = { | ||
33 | VALIDATORS: [ | ||
34 | Validators.minLength(3), | ||
35 | Validators.maxLength(1000) | ||
36 | ], | ||
37 | MESSAGES: { | ||
38 | 'minlength': $localize`Description must be at least 3 characters long.`, | ||
39 | 'maxlength': $localize`Description cannot be more than 1000 characters long.` | ||
40 | } | ||
41 | } | ||
42 | |||
43 | export const VIDEO_CHANNEL_SUPPORT_VALIDATOR: BuildFormValidator = { | ||
44 | VALIDATORS: [ | ||
45 | Validators.minLength(3), | ||
46 | Validators.maxLength(1000) | ||
47 | ], | ||
48 | MESSAGES: { | ||
49 | 'minlength': $localize`Support text must be at least 3 characters long.`, | ||
50 | 'maxlength': $localize`Support text cannot be more than 1000 characters long` | ||
51 | } | ||
52 | } | ||
diff --git a/client/src/app/shared/form-validators/video-comment-validators.ts b/client/src/app/shared/form-validators/video-comment-validators.ts new file mode 100644 index 000000000..c56564d34 --- /dev/null +++ b/client/src/app/shared/form-validators/video-comment-validators.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const VIDEO_COMMENT_TEXT_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ Validators.required, Validators.minLength(1), Validators.maxLength(3000) ], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`Comment is required.`, | ||
8 | 'minlength': $localize`Comment must be at least 2 characters long.`, | ||
9 | 'maxlength': $localize`Comment cannot be more than 3000 characters long.` | ||
10 | } | ||
11 | } | ||
diff --git a/client/src/app/shared/form-validators/video-ownership-change-validators.ts b/client/src/app/shared/form-validators/video-ownership-change-validators.ts new file mode 100644 index 000000000..e1a2df8a6 --- /dev/null +++ b/client/src/app/shared/form-validators/video-ownership-change-validators.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { AbstractControl, ValidationErrors, Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const OWNERSHIP_CHANGE_CHANNEL_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ Validators.required ], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`The channel is required.` | ||
8 | } | ||
9 | } | ||
10 | |||
11 | export const OWNERSHIP_CHANGE_USERNAME_VALIDATOR: BuildFormValidator = { | ||
12 | VALIDATORS: [ Validators.required, localAccountValidator ], | ||
13 | MESSAGES: { | ||
14 | 'required': $localize`The username is required.`, | ||
15 | 'localAccountOnly': $localize`You can only transfer ownership to a local account` | ||
16 | } | ||
17 | } | ||
18 | |||
19 | function localAccountValidator (control: AbstractControl): ValidationErrors { | ||
20 | if (control.value.includes('@')) { | ||
21 | return { 'localAccountOnly': true } | ||
22 | } | ||
23 | |||
24 | return null | ||
25 | } | ||
diff --git a/client/src/app/shared/form-validators/video-playlist-validators.ts b/client/src/app/shared/form-validators/video-playlist-validators.ts new file mode 100644 index 000000000..7e3d29458 --- /dev/null +++ b/client/src/app/shared/form-validators/video-playlist-validators.ts | |||
@@ -0,0 +1,54 @@ | |||
1 | import { Validators, AbstractControl } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | import { VideoPlaylistPrivacy } from '@shared/models' | ||
4 | |||
5 | export const VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR: BuildFormValidator = { | ||
6 | VALIDATORS: [ | ||
7 | Validators.required, | ||
8 | Validators.minLength(1), | ||
9 | Validators.maxLength(120) | ||
10 | ], | ||
11 | MESSAGES: { | ||
12 | 'required': $localize`Display name is required.`, | ||
13 | 'minlength': $localize`Display name must be at least 1 character long.`, | ||
14 | 'maxlength': $localize`Display name cannot be more than 120 characters long.` | ||
15 | } | ||
16 | } | ||
17 | |||
18 | export const VIDEO_PLAYLIST_PRIVACY_VALIDATOR: BuildFormValidator = { | ||
19 | VALIDATORS: [ | ||
20 | Validators.required | ||
21 | ], | ||
22 | MESSAGES: { | ||
23 | 'required': $localize`Privacy is required.` | ||
24 | } | ||
25 | } | ||
26 | |||
27 | export const VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR: BuildFormValidator = { | ||
28 | VALIDATORS: [ | ||
29 | Validators.minLength(3), | ||
30 | Validators.maxLength(1000) | ||
31 | ], | ||
32 | MESSAGES: { | ||
33 | 'minlength': $localize`Description must be at least 3 characters long.`, | ||
34 | 'maxlength': $localize`Description cannot be more than 1000 characters long.` | ||
35 | } | ||
36 | } | ||
37 | |||
38 | export const VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR: BuildFormValidator = { | ||
39 | VALIDATORS: [], | ||
40 | MESSAGES: { | ||
41 | 'required': $localize`The channel is required when the playlist is public.` | ||
42 | } | ||
43 | } | ||
44 | |||
45 | export function setPlaylistChannelValidator (channelControl: AbstractControl, privacy: VideoPlaylistPrivacy) { | ||
46 | if (privacy.toString() === VideoPlaylistPrivacy.PUBLIC.toString()) { | ||
47 | channelControl.setValidators([Validators.required]) | ||
48 | } else { | ||
49 | channelControl.setValidators(null) | ||
50 | } | ||
51 | |||
52 | channelControl.markAsDirty() | ||
53 | channelControl.updateValueAndValidity() | ||
54 | } | ||
diff --git a/client/src/app/shared/form-validators/video-validators.ts b/client/src/app/shared/form-validators/video-validators.ts new file mode 100644 index 000000000..23f2391b2 --- /dev/null +++ b/client/src/app/shared/form-validators/video-validators.ts | |||
@@ -0,0 +1,101 @@ | |||
1 | import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms' | ||
2 | import { BuildFormValidator } from './form-validator.model' | ||
3 | |||
4 | export const VIDEO_NAME_VALIDATOR: BuildFormValidator = { | ||
5 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(120) ], | ||
6 | MESSAGES: { | ||
7 | 'required': $localize`Video name is required.`, | ||
8 | 'minlength': $localize`Video name must be at least 3 characters long.`, | ||
9 | 'maxlength': $localize`Video name cannot be more than 120 characters long.` | ||
10 | } | ||
11 | } | ||
12 | |||
13 | export const VIDEO_PRIVACY_VALIDATOR: BuildFormValidator = { | ||
14 | VALIDATORS: [ Validators.required ], | ||
15 | MESSAGES: { | ||
16 | 'required': $localize`Video privacy is required.` | ||
17 | } | ||
18 | } | ||
19 | |||
20 | export const VIDEO_CATEGORY_VALIDATOR: BuildFormValidator = { | ||
21 | VALIDATORS: [ ], | ||
22 | MESSAGES: {} | ||
23 | } | ||
24 | |||
25 | export const VIDEO_LICENCE_VALIDATOR: BuildFormValidator = { | ||
26 | VALIDATORS: [ ], | ||
27 | MESSAGES: {} | ||
28 | } | ||
29 | |||
30 | export const VIDEO_LANGUAGE_VALIDATOR: BuildFormValidator = { | ||
31 | VALIDATORS: [ ], | ||
32 | MESSAGES: {} | ||
33 | } | ||
34 | |||
35 | export const VIDEO_IMAGE_VALIDATOR: BuildFormValidator = { | ||
36 | VALIDATORS: [ ], | ||
37 | MESSAGES: {} | ||
38 | } | ||
39 | |||
40 | export const VIDEO_CHANNEL_VALIDATOR: BuildFormValidator = { | ||
41 | VALIDATORS: [ Validators.required ], | ||
42 | MESSAGES: { | ||
43 | 'required': $localize`Video channel is required.` | ||
44 | } | ||
45 | } | ||
46 | |||
47 | export const VIDEO_DESCRIPTION_VALIDATOR: BuildFormValidator = { | ||
48 | VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ], | ||
49 | MESSAGES: { | ||
50 | 'minlength': $localize`Video description must be at least 3 characters long.`, | ||
51 | 'maxlength': $localize`Video description cannot be more than 10000 characters long.` | ||
52 | } | ||
53 | } | ||
54 | |||
55 | export const VIDEO_TAG_VALIDATOR: BuildFormValidator = { | ||
56 | VALIDATORS: [ Validators.minLength(2), Validators.maxLength(30) ], | ||
57 | MESSAGES: { | ||
58 | 'minlength': $localize`A tag should be more than 2 characters long.`, | ||
59 | 'maxlength': $localize`A tag should be less than 30 characters long.` | ||
60 | } | ||
61 | } | ||
62 | |||
63 | export const VIDEO_TAGS_ARRAY_VALIDATOR: BuildFormValidator = { | ||
64 | VALIDATORS: [ Validators.maxLength(5), arrayTagLengthValidator() ], | ||
65 | MESSAGES: { | ||
66 | 'maxlength': $localize`A maximum of 5 tags can be used on a video.`, | ||
67 | 'arrayTagLength': $localize`A tag should be more than 2, and less than 30 characters long.` | ||
68 | } | ||
69 | } | ||
70 | |||
71 | export const VIDEO_SUPPORT_VALIDATOR: BuildFormValidator = { | ||
72 | VALIDATORS: [ Validators.minLength(3), Validators.maxLength(1000) ], | ||
73 | MESSAGES: { | ||
74 | 'minlength': $localize`Video support must be at least 3 characters long.`, | ||
75 | 'maxlength': $localize`Video support cannot be more than 1000 characters long.` | ||
76 | } | ||
77 | } | ||
78 | |||
79 | export const VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR: BuildFormValidator = { | ||
80 | VALIDATORS: [ ], | ||
81 | MESSAGES: { | ||
82 | 'required': $localize`A date is required to schedule video update.` | ||
83 | } | ||
84 | } | ||
85 | |||
86 | export const VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR: BuildFormValidator = { | ||
87 | VALIDATORS: [ ], | ||
88 | MESSAGES: {} | ||
89 | } | ||
90 | |||
91 | function arrayTagLengthValidator (min = 2, max = 30): ValidatorFn { | ||
92 | return (control: AbstractControl): ValidationErrors => { | ||
93 | const array = control.value as Array<string> | ||
94 | |||
95 | if (array.every(e => e.length > min && e.length < max)) { | ||
96 | return null | ||
97 | } | ||
98 | |||
99 | return { 'arrayTagLength': true } | ||
100 | } | ||
101 | } | ||
diff --git a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts index 0c3c8ff48..9abb4c094 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { AuthService, HtmlRendererService, Notifier } from '@app/core' | 2 | import { AuthService, HtmlRendererService, Notifier } from '@app/core' |
3 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
6 | import { AbuseMessage, UserAbuse } from '@shared/models' | 6 | import { AbuseMessage, UserAbuse } from '@shared/models' |
7 | import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators' | ||
7 | import { AbuseService } from '../shared-moderation' | 8 | import { AbuseService } from '../shared-moderation' |
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
@@ -28,7 +29,6 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit { | |||
28 | 29 | ||
29 | constructor ( | 30 | constructor ( |
30 | protected formValidatorService: FormValidatorService, | 31 | protected formValidatorService: FormValidatorService, |
31 | private abuseValidatorsService: AbuseValidatorsService, | ||
32 | private modalService: NgbModal, | 32 | private modalService: NgbModal, |
33 | private htmlRenderer: HtmlRendererService, | 33 | private htmlRenderer: HtmlRendererService, |
34 | private auth: AuthService, | 34 | private auth: AuthService, |
@@ -40,7 +40,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit { | |||
40 | 40 | ||
41 | ngOnInit () { | 41 | ngOnInit () { |
42 | this.buildForm({ | 42 | this.buildForm({ |
43 | message: this.abuseValidatorsService.ABUSE_MESSAGE | 43 | message: ABUSE_MESSAGE_VALIDATOR |
44 | }) | 44 | }) |
45 | } | 45 | } |
46 | 46 | ||
diff --git a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts index fad7f888d..876aeea8a 100644 --- a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts +++ b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { AbuseService } from '@app/shared/shared-moderation' | 4 | import { AbuseService } from '@app/shared/shared-moderation' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
7 | import { AdminAbuse } from '@shared/models' | 7 | import { AdminAbuse } from '@shared/models' |
8 | import { ABUSE_MODERATION_COMMENT_VALIDATOR } from '../form-validators/abuse-validators' | ||
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
10 | selector: 'my-moderation-comment-modal', | 11 | selector: 'my-moderation-comment-modal', |
@@ -22,15 +23,14 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
22 | protected formValidatorService: FormValidatorService, | 23 | protected formValidatorService: FormValidatorService, |
23 | private modalService: NgbModal, | 24 | private modalService: NgbModal, |
24 | private notifier: Notifier, | 25 | private notifier: Notifier, |
25 | private abuseService: AbuseService, | 26 | private abuseService: AbuseService |
26 | private abuseValidatorsService: AbuseValidatorsService | ||
27 | ) { | 27 | ) { |
28 | super() | 28 | super() |
29 | } | 29 | } |
30 | 30 | ||
31 | ngOnInit () { | 31 | ngOnInit () { |
32 | this.buildForm({ | 32 | this.buildForm({ |
33 | moderationComment: this.abuseValidatorsService.ABUSE_MODERATION_COMMENT | 33 | moderationComment: ABUSE_MODERATION_COMMENT_VALIDATOR |
34 | }) | 34 | }) |
35 | } | 35 | } |
36 | 36 | ||
diff --git a/client/src/app/shared/shared-forms/dynamic-form-field.component.html b/client/src/app/shared/shared-forms/dynamic-form-field.component.html new file mode 100644 index 000000000..17b4a134f --- /dev/null +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.html | |||
@@ -0,0 +1,37 @@ | |||
1 | <div [formGroup]="form"> | ||
2 | <label *ngIf="setting.type !== 'input-checkbox'" [attr.for]="setting.name" [innerHTML]="setting.label"></label> | ||
3 | |||
4 | <div *ngIf="setting.descriptionHTML" class="label-small-info" [innerHTML]="setting.descriptionHTML"></div> | ||
5 | |||
6 | <input *ngIf="setting.type === 'input'" type="text" [id]="setting.name" [formControlName]="setting.name" /> | ||
7 | |||
8 | <textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea> | ||
9 | |||
10 | <my-help *ngIf="setting.type === 'markdown-text'" helpType="markdownText"></my-help> | ||
11 | |||
12 | <my-help *ngIf="setting.type === 'markdown-enhanced'" helpType="markdownEnhanced"></my-help> | ||
13 | |||
14 | <my-markdown-textarea | ||
15 | *ngIf="setting.type === 'markdown-text'" | ||
16 | markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | ||
17 | [classes]="{ 'input-error': formErrors['settings.name'] }" | ||
18 | ></my-markdown-textarea> | ||
19 | |||
20 | <my-markdown-textarea | ||
21 | *ngIf="setting.type === 'markdown-enhanced'" | ||
22 | markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | ||
23 | [classes]="{ 'input-error': formErrors['settings.name'] }" | ||
24 | ></my-markdown-textarea> | ||
25 | |||
26 | <my-peertube-checkbox | ||
27 | *ngIf="setting.type === 'input-checkbox'" | ||
28 | [id]="setting.name" | ||
29 | [formControlName]="setting.name" | ||
30 | [labelInnerHTML]="setting.label" | ||
31 | ></my-peertube-checkbox> | ||
32 | |||
33 | <div *ngIf="formErrors[setting.name]" class="form-error"> | ||
34 | {{ formErrors[setting.name] }} | ||
35 | </div> | ||
36 | |||
37 | </div> | ||
diff --git a/client/src/app/shared/shared-forms/dynamic-form-field.component.scss b/client/src/app/shared/shared-forms/dynamic-form-field.component.scss new file mode 100644 index 000000000..89193ed85 --- /dev/null +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.scss | |||
@@ -0,0 +1,24 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | input:not([type=submit]) { | ||
5 | @include peertube-input-text(340px); | ||
6 | |||
7 | display: block; | ||
8 | } | ||
9 | |||
10 | textarea { | ||
11 | @include peertube-textarea(340px, 200px); | ||
12 | |||
13 | display: block; | ||
14 | } | ||
15 | |||
16 | .peertube-select-container { | ||
17 | @include peertube-select-container(340px); | ||
18 | } | ||
19 | |||
20 | .label-small-info { | ||
21 | font-style: italic; | ||
22 | margin-bottom: 10px; | ||
23 | font-size: 13px; | ||
24 | } | ||
diff --git a/client/src/app/shared/shared-forms/dynamic-form-field.component.ts b/client/src/app/shared/shared-forms/dynamic-form-field.component.ts new file mode 100644 index 000000000..b63890797 --- /dev/null +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.ts | |||
@@ -0,0 +1,15 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { FormGroup } from '@angular/forms' | ||
3 | import { RegisterClientFormFieldOptions } from '@shared/models' | ||
4 | |||
5 | @Component({ | ||
6 | selector: 'my-dynamic-form-field', | ||
7 | templateUrl: './dynamic-form-field.component.html', | ||
8 | styleUrls: [ './dynamic-form-field.component.scss' ] | ||
9 | }) | ||
10 | |||
11 | export class DynamicFormFieldComponent { | ||
12 | @Input() form: FormGroup | ||
13 | @Input() formErrors: any | ||
14 | @Input() setting: RegisterClientFormFieldOptions | ||
15 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts index caa31d831..adf6cb894 100644 --- a/client/src/app/shared/shared-forms/form-reactive.ts +++ b/client/src/app/shared/shared-forms/form-reactive.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { FormGroup } from '@angular/forms' | 1 | import { FormGroup } from '@angular/forms' |
2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from './form-validators' | 2 | import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model' |
3 | import { FormValidatorService } from './form-validator.service' | ||
3 | 4 | ||
4 | export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } | 5 | export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } |
5 | export type FormReactiveValidationMessages = { | 6 | export type FormReactiveValidationMessages = { |
diff --git a/client/src/app/shared/shared-forms/form-validators/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validator.service.ts index dec7d8d9a..41c8b76bd 100644 --- a/client/src/app/shared/shared-forms/form-validators/form-validator.service.ts +++ b/client/src/app/shared/shared-forms/form-validator.service.ts | |||
@@ -1,17 +1,7 @@ | |||
1 | import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' | ||
2 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
3 | import { FormReactiveErrors, FormReactiveValidationMessages } from '../form-reactive' | 2 | import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' |
4 | 3 | import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model' | |
5 | export type BuildFormValidator = { | 4 | import { FormReactiveErrors, FormReactiveValidationMessages } from './form-reactive' |
6 | VALIDATORS: ValidatorFn[], | ||
7 | MESSAGES: { [ name: string ]: string } | ||
8 | } | ||
9 | export type BuildFormArgument = { | ||
10 | [ id: string ]: BuildFormValidator | BuildFormArgument | ||
11 | } | ||
12 | export type BuildFormDefaultValues = { | ||
13 | [ name: string ]: string | string[] | BuildFormDefaultValues | ||
14 | } | ||
15 | 5 | ||
16 | @Injectable() | 6 | @Injectable() |
17 | export class FormValidatorService { | 7 | export class FormValidatorService { |
diff --git a/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts deleted file mode 100644 index 56d30d6f9..000000000 --- a/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class AbuseValidatorsService { | ||
7 | readonly ABUSE_REASON: BuildFormValidator | ||
8 | readonly ABUSE_MODERATION_COMMENT: BuildFormValidator | ||
9 | readonly ABUSE_MESSAGE: BuildFormValidator | ||
10 | |||
11 | constructor () { | ||
12 | this.ABUSE_REASON = { | ||
13 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], | ||
14 | MESSAGES: { | ||
15 | 'required': $localize`Report reason is required.`, | ||
16 | 'minlength': $localize`Report reason must be at least 2 characters long.`, | ||
17 | 'maxlength': $localize`Report reason cannot be more than 3000 characters long.` | ||
18 | } | ||
19 | } | ||
20 | |||
21 | this.ABUSE_MODERATION_COMMENT = { | ||
22 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], | ||
23 | MESSAGES: { | ||
24 | 'required': $localize`Moderation comment is required.`, | ||
25 | 'minlength': $localize`Moderation comment must be at least 2 characters long.`, | ||
26 | 'maxlength': $localize`Moderation comment cannot be more than 3000 characters long.` | ||
27 | } | ||
28 | } | ||
29 | |||
30 | this.ABUSE_MESSAGE = { | ||
31 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], | ||
32 | MESSAGES: { | ||
33 | 'required': $localize`Abuse message is required.`, | ||
34 | 'minlength': $localize`Abuse message must be at least 2 characters long.`, | ||
35 | 'maxlength': $localize`Abuse message cannot be more than 3000 characters long.` | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts deleted file mode 100644 index 6c7da833f..000000000 --- a/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts +++ /dev/null | |||
@@ -1,68 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { ValidatorFn, Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | import { validateHost } from './host' | ||
5 | |||
6 | @Injectable() | ||
7 | export class BatchDomainsValidatorsService { | ||
8 | readonly DOMAINS: BuildFormValidator | ||
9 | |||
10 | constructor () { | ||
11 | this.DOMAINS = { | ||
12 | VALIDATORS: [ Validators.required, this.validDomains, this.isHostsUnique ], | ||
13 | MESSAGES: { | ||
14 | 'required': $localize`Domain is required.`, | ||
15 | 'validDomains': $localize`Domains entered are invalid.`, | ||
16 | 'uniqueDomains': $localize`Domains entered contain duplicates.` | ||
17 | } | ||
18 | } | ||
19 | } | ||
20 | |||
21 | getNotEmptyHosts (hosts: string) { | ||
22 | return hosts | ||
23 | .split('\n') | ||
24 | .filter((host: string) => host && host.length !== 0) // Eject empty hosts | ||
25 | } | ||
26 | |||
27 | private validDomains: ValidatorFn = (control) => { | ||
28 | if (!control.value) return null | ||
29 | |||
30 | const newHostsErrors = [] | ||
31 | const hosts = this.getNotEmptyHosts(control.value) | ||
32 | |||
33 | for (const host of hosts) { | ||
34 | if (validateHost(host) === false) { | ||
35 | newHostsErrors.push($localize`${host} is not valid`) | ||
36 | } | ||
37 | } | ||
38 | |||
39 | /* Is not valid. */ | ||
40 | if (newHostsErrors.length !== 0) { | ||
41 | return { | ||
42 | 'validDomains': { | ||
43 | reason: 'invalid', | ||
44 | value: newHostsErrors.join('. ') + '.' | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | /* Is valid. */ | ||
50 | return null | ||
51 | } | ||
52 | |||
53 | private isHostsUnique: ValidatorFn = (control) => { | ||
54 | if (!control.value) return null | ||
55 | |||
56 | const hosts = this.getNotEmptyHosts(control.value) | ||
57 | |||
58 | if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) { | ||
59 | return null | ||
60 | } else { | ||
61 | return { | ||
62 | 'uniqueDomains': { | ||
63 | reason: 'invalid' | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts deleted file mode 100644 index 862ff5470..000000000 --- a/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts +++ /dev/null | |||
@@ -1,97 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class CustomConfigValidatorsService { | ||
7 | readonly INSTANCE_NAME: BuildFormValidator | ||
8 | readonly INSTANCE_SHORT_DESCRIPTION: BuildFormValidator | ||
9 | readonly SERVICES_TWITTER_USERNAME: BuildFormValidator | ||
10 | readonly CACHE_PREVIEWS_SIZE: BuildFormValidator | ||
11 | readonly CACHE_CAPTIONS_SIZE: BuildFormValidator | ||
12 | readonly SIGNUP_LIMIT: BuildFormValidator | ||
13 | readonly ADMIN_EMAIL: BuildFormValidator | ||
14 | readonly TRANSCODING_THREADS: BuildFormValidator | ||
15 | readonly INDEX_URL: BuildFormValidator | ||
16 | readonly SEARCH_INDEX_URL: BuildFormValidator | ||
17 | |||
18 | constructor () { | ||
19 | this.INSTANCE_NAME = { | ||
20 | VALIDATORS: [ Validators.required ], | ||
21 | MESSAGES: { | ||
22 | 'required': $localize`Instance name is required.` | ||
23 | } | ||
24 | } | ||
25 | |||
26 | this.INSTANCE_SHORT_DESCRIPTION = { | ||
27 | VALIDATORS: [ Validators.max(250) ], | ||
28 | MESSAGES: { | ||
29 | 'max': $localize`Short description should not be longer than 250 characters.` | ||
30 | } | ||
31 | } | ||
32 | |||
33 | this.SERVICES_TWITTER_USERNAME = { | ||
34 | VALIDATORS: [ Validators.required ], | ||
35 | MESSAGES: { | ||
36 | 'required': $localize`Twitter username is required.` | ||
37 | } | ||
38 | } | ||
39 | |||
40 | this.CACHE_PREVIEWS_SIZE = { | ||
41 | VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], | ||
42 | MESSAGES: { | ||
43 | 'required': $localize`Previews cache size is required.`, | ||
44 | 'min': $localize`Previews cache size must be greater than 1.`, | ||
45 | 'pattern': $localize`Previews cache size must be a number.` | ||
46 | } | ||
47 | } | ||
48 | |||
49 | this.CACHE_CAPTIONS_SIZE = { | ||
50 | VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], | ||
51 | MESSAGES: { | ||
52 | 'required': $localize`Captions cache size is required.`, | ||
53 | 'min': $localize`Captions cache size must be greater than 1.`, | ||
54 | 'pattern': $localize`Captions cache size must be a number.` | ||
55 | } | ||
56 | } | ||
57 | |||
58 | this.SIGNUP_LIMIT = { | ||
59 | VALIDATORS: [ Validators.required, Validators.min(-1), Validators.pattern('-?[0-9]+') ], | ||
60 | MESSAGES: { | ||
61 | 'required': $localize`Signup limit is required.`, | ||
62 | 'min': $localize`Signup limit must be greater than 1.`, | ||
63 | 'pattern': $localize`Signup limit must be a number.` | ||
64 | } | ||
65 | } | ||
66 | |||
67 | this.ADMIN_EMAIL = { | ||
68 | VALIDATORS: [ Validators.required, Validators.email ], | ||
69 | MESSAGES: { | ||
70 | 'required': $localize`Admin email is required.`, | ||
71 | 'email': $localize`Admin email must be valid.` | ||
72 | } | ||
73 | } | ||
74 | |||
75 | this.TRANSCODING_THREADS = { | ||
76 | VALIDATORS: [ Validators.required, Validators.min(0) ], | ||
77 | MESSAGES: { | ||
78 | 'required': $localize`Transcoding threads is required.`, | ||
79 | 'min': $localize`Transcoding threads must be greater or equal to 0.` | ||
80 | } | ||
81 | } | ||
82 | |||
83 | this.INDEX_URL = { | ||
84 | VALIDATORS: [ Validators.pattern(/^https:\/\//) ], | ||
85 | MESSAGES: { | ||
86 | 'pattern': $localize`Index URL should be a URL` | ||
87 | } | ||
88 | } | ||
89 | |||
90 | this.SEARCH_INDEX_URL = { | ||
91 | VALIDATORS: [ Validators.pattern(/^https?:\/\//) ], | ||
92 | MESSAGES: { | ||
93 | 'pattern': $localize`Search index URL should be a URL` | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/index.ts b/client/src/app/shared/shared-forms/form-validators/index.ts deleted file mode 100644 index b06a326ff..000000000 --- a/client/src/app/shared/shared-forms/form-validators/index.ts +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | export * from './abuse-validators.service' | ||
2 | export * from './batch-domains-validators.service' | ||
3 | export * from './custom-config-validators.service' | ||
4 | export * from './form-validator.service' | ||
5 | export * from './host' | ||
6 | export * from './instance-validators.service' | ||
7 | export * from './login-validators.service' | ||
8 | export * from './reset-password-validators.service' | ||
9 | export * from './user-validators.service' | ||
10 | export * from './video-accept-ownership-validators.service' | ||
11 | export * from './video-block-validators.service' | ||
12 | export * from './video-captions-validators.service' | ||
13 | export * from './video-change-ownership-validators.service' | ||
14 | export * from './video-channel-validators.service' | ||
15 | export * from './video-comment-validators.service' | ||
16 | export * from './video-playlist-validators.service' | ||
17 | export * from './video-validators.service' | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts deleted file mode 100644 index 3628f0b60..000000000 --- a/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts +++ /dev/null | |||
@@ -1,61 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class InstanceValidatorsService { | ||
7 | readonly FROM_EMAIL: BuildFormValidator | ||
8 | readonly FROM_NAME: BuildFormValidator | ||
9 | readonly SUBJECT: BuildFormValidator | ||
10 | readonly BODY: BuildFormValidator | ||
11 | |||
12 | constructor () { | ||
13 | |||
14 | this.FROM_EMAIL = { | ||
15 | VALIDATORS: [ Validators.required, Validators.email ], | ||
16 | MESSAGES: { | ||
17 | 'required': $localize`Email is required.`, | ||
18 | 'email': $localize`Email must be valid.` | ||
19 | } | ||
20 | } | ||
21 | |||
22 | this.FROM_NAME = { | ||
23 | VALIDATORS: [ | ||
24 | Validators.required, | ||
25 | Validators.minLength(1), | ||
26 | Validators.maxLength(120) | ||
27 | ], | ||
28 | MESSAGES: { | ||
29 | 'required': $localize`Your name is required.`, | ||
30 | 'minlength': $localize`Your name must be at least 1 character long.`, | ||
31 | 'maxlength': $localize`Your name cannot be more than 120 characters long.` | ||
32 | } | ||
33 | } | ||
34 | |||
35 | this.SUBJECT = { | ||
36 | VALIDATORS: [ | ||
37 | Validators.required, | ||
38 | Validators.minLength(1), | ||
39 | Validators.maxLength(120) | ||
40 | ], | ||
41 | MESSAGES: { | ||
42 | 'required': $localize`A subject is required.`, | ||
43 | 'minlength': $localize`The subject must be at least 1 character long.`, | ||
44 | 'maxlength': $localize`The subject cannot be more than 120 characters long.` | ||
45 | } | ||
46 | } | ||
47 | |||
48 | this.BODY = { | ||
49 | VALIDATORS: [ | ||
50 | Validators.required, | ||
51 | Validators.minLength(3), | ||
52 | Validators.maxLength(5000) | ||
53 | ], | ||
54 | MESSAGES: { | ||
55 | 'required': $localize`A message is required.`, | ||
56 | 'minlength': $localize`The message must be at least 3 characters long.`, | ||
57 | 'maxlength': $localize`The message cannot be more than 5000 characters long.` | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts deleted file mode 100644 index 67ea11f20..000000000 --- a/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class LoginValidatorsService { | ||
7 | readonly LOGIN_USERNAME: BuildFormValidator | ||
8 | readonly LOGIN_PASSWORD: BuildFormValidator | ||
9 | |||
10 | constructor () { | ||
11 | this.LOGIN_USERNAME = { | ||
12 | VALIDATORS: [ | ||
13 | Validators.required | ||
14 | ], | ||
15 | MESSAGES: { | ||
16 | 'required': $localize`Username is required.` | ||
17 | } | ||
18 | } | ||
19 | |||
20 | this.LOGIN_PASSWORD = { | ||
21 | VALIDATORS: [ | ||
22 | Validators.required | ||
23 | ], | ||
24 | MESSAGES: { | ||
25 | 'required': $localize`Password is required.` | ||
26 | } | ||
27 | } | ||
28 | } | ||
29 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts deleted file mode 100644 index 3d0b4dd64..000000000 --- a/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class ResetPasswordValidatorsService { | ||
7 | readonly RESET_PASSWORD_CONFIRM: BuildFormValidator | ||
8 | |||
9 | constructor () { | ||
10 | this.RESET_PASSWORD_CONFIRM = { | ||
11 | VALIDATORS: [ | ||
12 | Validators.required | ||
13 | ], | ||
14 | MESSAGES: { | ||
15 | 'required': $localize`Confirmation of the password is required.` | ||
16 | } | ||
17 | } | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts deleted file mode 100644 index 312fc9b1e..000000000 --- a/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts +++ /dev/null | |||
@@ -1,166 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class UserValidatorsService { | ||
7 | readonly USER_USERNAME: BuildFormValidator | ||
8 | readonly USER_CHANNEL_NAME: BuildFormValidator | ||
9 | readonly USER_EMAIL: BuildFormValidator | ||
10 | readonly USER_PASSWORD: BuildFormValidator | ||
11 | readonly USER_PASSWORD_OPTIONAL: BuildFormValidator | ||
12 | readonly USER_CONFIRM_PASSWORD: BuildFormValidator | ||
13 | readonly USER_VIDEO_QUOTA: BuildFormValidator | ||
14 | readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator | ||
15 | readonly USER_ROLE: BuildFormValidator | ||
16 | readonly USER_DISPLAY_NAME_REQUIRED: BuildFormValidator | ||
17 | readonly USER_DESCRIPTION: BuildFormValidator | ||
18 | readonly USER_TERMS: BuildFormValidator | ||
19 | |||
20 | readonly USER_BAN_REASON: BuildFormValidator | ||
21 | |||
22 | constructor () { | ||
23 | |||
24 | this.USER_USERNAME = { | ||
25 | VALIDATORS: [ | ||
26 | Validators.required, | ||
27 | Validators.minLength(1), | ||
28 | Validators.maxLength(50), | ||
29 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | ||
30 | ], | ||
31 | MESSAGES: { | ||
32 | 'required': $localize`Username is required.`, | ||
33 | 'minlength': $localize`Username must be at least 1 character long.`, | ||
34 | 'maxlength': $localize`Username cannot be more than 50 characters long.`, | ||
35 | 'pattern': $localize`Username should be lowercase alphanumeric; dots and underscores are allowed.` | ||
36 | } | ||
37 | } | ||
38 | |||
39 | this.USER_CHANNEL_NAME = { | ||
40 | VALIDATORS: [ | ||
41 | Validators.required, | ||
42 | Validators.minLength(1), | ||
43 | Validators.maxLength(50), | ||
44 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | ||
45 | ], | ||
46 | MESSAGES: { | ||
47 | 'required': $localize`Channel name is required.`, | ||
48 | 'minlength': $localize`Channel name must be at least 1 character long.`, | ||
49 | 'maxlength': $localize`Channel name cannot be more than 50 characters long.`, | ||
50 | 'pattern': $localize`Channel name should be lowercase alphanumeric; dots and underscores are allowed.` | ||
51 | } | ||
52 | } | ||
53 | |||
54 | this.USER_EMAIL = { | ||
55 | VALIDATORS: [ Validators.required, Validators.email ], | ||
56 | MESSAGES: { | ||
57 | 'required': $localize`Email is required.`, | ||
58 | 'email': $localize`Email must be valid.` | ||
59 | } | ||
60 | } | ||
61 | |||
62 | this.USER_PASSWORD = { | ||
63 | VALIDATORS: [ | ||
64 | Validators.required, | ||
65 | Validators.minLength(6), | ||
66 | Validators.maxLength(255) | ||
67 | ], | ||
68 | MESSAGES: { | ||
69 | 'required': $localize`Password is required.`, | ||
70 | 'minlength': $localize`Password must be at least 6 characters long.`, | ||
71 | 'maxlength': $localize`Password cannot be more than 255 characters long.` | ||
72 | } | ||
73 | } | ||
74 | |||
75 | this.USER_PASSWORD_OPTIONAL = { | ||
76 | VALIDATORS: [ | ||
77 | Validators.minLength(6), | ||
78 | Validators.maxLength(255) | ||
79 | ], | ||
80 | MESSAGES: { | ||
81 | 'minlength': $localize`Password must be at least 6 characters long.`, | ||
82 | 'maxlength': $localize`Password cannot be more than 255 characters long.` | ||
83 | } | ||
84 | } | ||
85 | |||
86 | this.USER_CONFIRM_PASSWORD = { | ||
87 | VALIDATORS: [], | ||
88 | MESSAGES: { | ||
89 | 'matchPassword': $localize`The new password and the confirmed password do not correspond.` | ||
90 | } | ||
91 | } | ||
92 | |||
93 | this.USER_VIDEO_QUOTA = { | ||
94 | VALIDATORS: [ Validators.required, Validators.min(-1) ], | ||
95 | MESSAGES: { | ||
96 | 'required': $localize`Video quota is required.`, | ||
97 | 'min': $localize`Quota must be greater than -1.` | ||
98 | } | ||
99 | } | ||
100 | this.USER_VIDEO_QUOTA_DAILY = { | ||
101 | VALIDATORS: [ Validators.required, Validators.min(-1) ], | ||
102 | MESSAGES: { | ||
103 | 'required': $localize`Daily upload limit is required.`, | ||
104 | 'min': $localize`Daily upload limit must be greater than -1.` | ||
105 | } | ||
106 | } | ||
107 | |||
108 | this.USER_ROLE = { | ||
109 | VALIDATORS: [ Validators.required ], | ||
110 | MESSAGES: { | ||
111 | 'required': $localize`User role is required.` | ||
112 | } | ||
113 | } | ||
114 | |||
115 | this.USER_DISPLAY_NAME_REQUIRED = this.getDisplayName(true) | ||
116 | |||
117 | this.USER_DESCRIPTION = { | ||
118 | VALIDATORS: [ | ||
119 | Validators.minLength(3), | ||
120 | Validators.maxLength(1000) | ||
121 | ], | ||
122 | MESSAGES: { | ||
123 | 'minlength': $localize`Description must be at least 3 characters long.`, | ||
124 | 'maxlength': $localize`Description cannot be more than 1000 characters long.` | ||
125 | } | ||
126 | } | ||
127 | |||
128 | this.USER_TERMS = { | ||
129 | VALIDATORS: [ | ||
130 | Validators.requiredTrue | ||
131 | ], | ||
132 | MESSAGES: { | ||
133 | 'required': $localize`You must agree with the instance terms in order to register on it.` | ||
134 | } | ||
135 | } | ||
136 | |||
137 | this.USER_BAN_REASON = { | ||
138 | VALIDATORS: [ | ||
139 | Validators.minLength(3), | ||
140 | Validators.maxLength(250) | ||
141 | ], | ||
142 | MESSAGES: { | ||
143 | 'minlength': $localize`Ban reason must be at least 3 characters long.`, | ||
144 | 'maxlength': $localize`Ban reason cannot be more than 250 characters long.` | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | private getDisplayName (required: boolean) { | ||
150 | const control = { | ||
151 | VALIDATORS: [ | ||
152 | Validators.minLength(1), | ||
153 | Validators.maxLength(120) | ||
154 | ], | ||
155 | MESSAGES: { | ||
156 | 'required': $localize`Display name is required.`, | ||
157 | 'minlength': $localize`Display name must be at least 1 character long.`, | ||
158 | 'maxlength': $localize`Display name cannot be more than 50 characters long.` | ||
159 | } | ||
160 | } | ||
161 | |||
162 | if (required) control.VALIDATORS.push(Validators.required) | ||
163 | |||
164 | return control | ||
165 | } | ||
166 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts deleted file mode 100644 index aed9e9cdd..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoAcceptOwnershipValidatorsService { | ||
7 | readonly CHANNEL: BuildFormValidator | ||
8 | |||
9 | constructor () { | ||
10 | this.CHANNEL = { | ||
11 | VALIDATORS: [ Validators.required ], | ||
12 | MESSAGES: { | ||
13 | 'required': $localize`The channel is required.` | ||
14 | } | ||
15 | } | ||
16 | } | ||
17 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts deleted file mode 100644 index bce1880dc..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoBlockValidatorsService { | ||
7 | readonly VIDEO_BLOCK_REASON: BuildFormValidator | ||
8 | |||
9 | constructor () { | ||
10 | this.VIDEO_BLOCK_REASON = { | ||
11 | VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ], | ||
12 | MESSAGES: { | ||
13 | 'minlength': $localize`Block reason must be at least 2 characters long.`, | ||
14 | 'maxlength': $localize`Block reason cannot be more than 300 characters long.` | ||
15 | } | ||
16 | } | ||
17 | } | ||
18 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts deleted file mode 100644 index 7e90264e5..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoCaptionsValidatorsService { | ||
7 | readonly VIDEO_CAPTION_LANGUAGE: BuildFormValidator | ||
8 | readonly VIDEO_CAPTION_FILE: BuildFormValidator | ||
9 | |||
10 | constructor () { | ||
11 | |||
12 | this.VIDEO_CAPTION_LANGUAGE = { | ||
13 | VALIDATORS: [ Validators.required ], | ||
14 | MESSAGES: { | ||
15 | 'required': $localize`Video caption language is required.` | ||
16 | } | ||
17 | } | ||
18 | |||
19 | this.VIDEO_CAPTION_FILE = { | ||
20 | VALIDATORS: [ Validators.required ], | ||
21 | MESSAGES: { | ||
22 | 'required': $localize`Video caption file is required.` | ||
23 | } | ||
24 | } | ||
25 | } | ||
26 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts deleted file mode 100644 index 8c809a0d5..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { AbstractControl, ValidationErrors, Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoChangeOwnershipValidatorsService { | ||
7 | readonly USERNAME: BuildFormValidator | ||
8 | |||
9 | constructor () { | ||
10 | this.USERNAME = { | ||
11 | VALIDATORS: [ Validators.required, this.localAccountValidator ], | ||
12 | MESSAGES: { | ||
13 | 'required': $localize`The username is required.`, | ||
14 | 'localAccountOnly': $localize`You can only transfer ownership to a local account` | ||
15 | } | ||
16 | } | ||
17 | } | ||
18 | |||
19 | localAccountValidator (control: AbstractControl): ValidationErrors { | ||
20 | if (control.value.includes('@')) { | ||
21 | return { 'localAccountOnly': true } | ||
22 | } | ||
23 | |||
24 | return null | ||
25 | } | ||
26 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts deleted file mode 100644 index 3e7444196..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts +++ /dev/null | |||
@@ -1,63 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoChannelValidatorsService { | ||
7 | readonly VIDEO_CHANNEL_NAME: BuildFormValidator | ||
8 | readonly VIDEO_CHANNEL_DISPLAY_NAME: BuildFormValidator | ||
9 | readonly VIDEO_CHANNEL_DESCRIPTION: BuildFormValidator | ||
10 | readonly VIDEO_CHANNEL_SUPPORT: BuildFormValidator | ||
11 | |||
12 | constructor () { | ||
13 | this.VIDEO_CHANNEL_NAME = { | ||
14 | VALIDATORS: [ | ||
15 | Validators.required, | ||
16 | Validators.minLength(1), | ||
17 | Validators.maxLength(50), | ||
18 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | ||
19 | ], | ||
20 | MESSAGES: { | ||
21 | 'required': $localize`Name is required.`, | ||
22 | 'minlength': $localize`Name must be at least 1 character long.`, | ||
23 | 'maxlength': $localize`Name cannot be more than 50 characters long.`, | ||
24 | 'pattern': $localize`Name should be lowercase alphanumeric; dots and underscores are allowed.` | ||
25 | } | ||
26 | } | ||
27 | |||
28 | this.VIDEO_CHANNEL_DISPLAY_NAME = { | ||
29 | VALIDATORS: [ | ||
30 | Validators.required, | ||
31 | Validators.minLength(1), | ||
32 | Validators.maxLength(50) | ||
33 | ], | ||
34 | MESSAGES: { | ||
35 | 'required': $localize`Display name is required.`, | ||
36 | 'minlength': $localize`Display name must be at least 1 character long.`, | ||
37 | 'maxlength': $localize`Display name cannot be more than 50 characters long.` | ||
38 | } | ||
39 | } | ||
40 | |||
41 | this.VIDEO_CHANNEL_DESCRIPTION = { | ||
42 | VALIDATORS: [ | ||
43 | Validators.minLength(3), | ||
44 | Validators.maxLength(1000) | ||
45 | ], | ||
46 | MESSAGES: { | ||
47 | 'minlength': $localize`Description must be at least 3 characters long.`, | ||
48 | 'maxlength': $localize`Description cannot be more than 1000 characters long.` | ||
49 | } | ||
50 | } | ||
51 | |||
52 | this.VIDEO_CHANNEL_SUPPORT = { | ||
53 | VALIDATORS: [ | ||
54 | Validators.minLength(3), | ||
55 | Validators.maxLength(1000) | ||
56 | ], | ||
57 | MESSAGES: { | ||
58 | 'minlength': $localize`Support text must be at least 3 characters long.`, | ||
59 | 'maxlength': $localize`Support text cannot be more than 1000 characters long` | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts deleted file mode 100644 index 18e7ae264..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoCommentValidatorsService { | ||
7 | readonly VIDEO_COMMENT_TEXT: BuildFormValidator | ||
8 | |||
9 | constructor () { | ||
10 | this.VIDEO_COMMENT_TEXT = { | ||
11 | VALIDATORS: [ Validators.required, Validators.minLength(1), Validators.maxLength(3000) ], | ||
12 | MESSAGES: { | ||
13 | 'required': $localize`Comment is required.`, | ||
14 | 'minlength': $localize`Comment must be at least 2 characters long.`, | ||
15 | 'maxlength': $localize`Comment cannot be more than 3000 characters long.` | ||
16 | } | ||
17 | } | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts deleted file mode 100644 index 3b45a40fd..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts +++ /dev/null | |||
@@ -1,65 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { AbstractControl, Validators } from '@angular/forms' | ||
3 | import { VideoPlaylistPrivacy } from '@shared/models' | ||
4 | import { BuildFormValidator } from './form-validator.service' | ||
5 | |||
6 | @Injectable() | ||
7 | export class VideoPlaylistValidatorsService { | ||
8 | readonly VIDEO_PLAYLIST_DISPLAY_NAME: BuildFormValidator | ||
9 | readonly VIDEO_PLAYLIST_PRIVACY: BuildFormValidator | ||
10 | readonly VIDEO_PLAYLIST_DESCRIPTION: BuildFormValidator | ||
11 | readonly VIDEO_PLAYLIST_CHANNEL_ID: BuildFormValidator | ||
12 | |||
13 | constructor () { | ||
14 | this.VIDEO_PLAYLIST_DISPLAY_NAME = { | ||
15 | VALIDATORS: [ | ||
16 | Validators.required, | ||
17 | Validators.minLength(1), | ||
18 | Validators.maxLength(120) | ||
19 | ], | ||
20 | MESSAGES: { | ||
21 | 'required': $localize`Display name is required.`, | ||
22 | 'minlength': $localize`Display name must be at least 1 character long.`, | ||
23 | 'maxlength': $localize`Display name cannot be more than 120 characters long.` | ||
24 | } | ||
25 | } | ||
26 | |||
27 | this.VIDEO_PLAYLIST_PRIVACY = { | ||
28 | VALIDATORS: [ | ||
29 | Validators.required | ||
30 | ], | ||
31 | MESSAGES: { | ||
32 | 'required': $localize`Privacy is required.` | ||
33 | } | ||
34 | } | ||
35 | |||
36 | this.VIDEO_PLAYLIST_DESCRIPTION = { | ||
37 | VALIDATORS: [ | ||
38 | Validators.minLength(3), | ||
39 | Validators.maxLength(1000) | ||
40 | ], | ||
41 | MESSAGES: { | ||
42 | 'minlength': $localize`Description must be at least 3 characters long.`, | ||
43 | 'maxlength': $localize`Description cannot be more than 1000 characters long.` | ||
44 | } | ||
45 | } | ||
46 | |||
47 | this.VIDEO_PLAYLIST_CHANNEL_ID = { | ||
48 | VALIDATORS: [ ], | ||
49 | MESSAGES: { | ||
50 | 'required': $localize`The channel is required when the playlist is public.` | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | |||
55 | setChannelValidator (channelControl: AbstractControl, privacy: VideoPlaylistPrivacy) { | ||
56 | if (privacy.toString() === VideoPlaylistPrivacy.PUBLIC.toString()) { | ||
57 | channelControl.setValidators([ Validators.required ]) | ||
58 | } else { | ||
59 | channelControl.setValidators(null) | ||
60 | } | ||
61 | |||
62 | channelControl.markAsDirty() | ||
63 | channelControl.updateValueAndValidity() | ||
64 | } | ||
65 | } | ||
diff --git a/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts deleted file mode 100644 index 8119c1ae7..000000000 --- a/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts +++ /dev/null | |||
@@ -1,122 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from './form-validator.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class VideoValidatorsService { | ||
7 | readonly VIDEO_NAME: BuildFormValidator | ||
8 | readonly VIDEO_PRIVACY: BuildFormValidator | ||
9 | readonly VIDEO_CATEGORY: BuildFormValidator | ||
10 | readonly VIDEO_LICENCE: BuildFormValidator | ||
11 | readonly VIDEO_LANGUAGE: BuildFormValidator | ||
12 | readonly VIDEO_IMAGE: BuildFormValidator | ||
13 | readonly VIDEO_CHANNEL: BuildFormValidator | ||
14 | readonly VIDEO_DESCRIPTION: BuildFormValidator | ||
15 | readonly VIDEO_TAGS_ARRAY: BuildFormValidator | ||
16 | readonly VIDEO_TAG: BuildFormValidator | ||
17 | readonly VIDEO_SUPPORT: BuildFormValidator | ||
18 | readonly VIDEO_SCHEDULE_PUBLICATION_AT: BuildFormValidator | ||
19 | readonly VIDEO_ORIGINALLY_PUBLISHED_AT: BuildFormValidator | ||
20 | |||
21 | constructor () { | ||
22 | |||
23 | this.VIDEO_NAME = { | ||
24 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(120) ], | ||
25 | MESSAGES: { | ||
26 | 'required': $localize`Video name is required.`, | ||
27 | 'minlength': $localize`Video name must be at least 3 characters long.`, | ||
28 | 'maxlength': $localize`Video name cannot be more than 120 characters long.` | ||
29 | } | ||
30 | } | ||
31 | |||
32 | this.VIDEO_PRIVACY = { | ||
33 | VALIDATORS: [ Validators.required ], | ||
34 | MESSAGES: { | ||
35 | 'required': $localize`Video privacy is required.` | ||
36 | } | ||
37 | } | ||
38 | |||
39 | this.VIDEO_CATEGORY = { | ||
40 | VALIDATORS: [ ], | ||
41 | MESSAGES: {} | ||
42 | } | ||
43 | |||
44 | this.VIDEO_LICENCE = { | ||
45 | VALIDATORS: [ ], | ||
46 | MESSAGES: {} | ||
47 | } | ||
48 | |||
49 | this.VIDEO_LANGUAGE = { | ||
50 | VALIDATORS: [ ], | ||
51 | MESSAGES: {} | ||
52 | } | ||
53 | |||
54 | this.VIDEO_IMAGE = { | ||
55 | VALIDATORS: [ ], | ||
56 | MESSAGES: {} | ||
57 | } | ||
58 | |||
59 | this.VIDEO_CHANNEL = { | ||
60 | VALIDATORS: [ Validators.required ], | ||
61 | MESSAGES: { | ||
62 | 'required': $localize`Video channel is required.` | ||
63 | } | ||
64 | } | ||
65 | |||
66 | this.VIDEO_DESCRIPTION = { | ||
67 | VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ], | ||
68 | MESSAGES: { | ||
69 | 'minlength': $localize`Video description must be at least 3 characters long.`, | ||
70 | 'maxlength': $localize`Video description cannot be more than 10000 characters long.` | ||
71 | } | ||
72 | } | ||
73 | |||
74 | this.VIDEO_TAG = { | ||
75 | VALIDATORS: [ Validators.minLength(2), Validators.maxLength(30) ], | ||
76 | MESSAGES: { | ||
77 | 'minlength': $localize`A tag should be more than 2 characters long.`, | ||
78 | 'maxlength': $localize`A tag should be less than 30 characters long.` | ||
79 | } | ||
80 | } | ||
81 | |||
82 | this.VIDEO_TAGS_ARRAY = { | ||
83 | VALIDATORS: [ Validators.maxLength(5), this.arrayTagLengthValidator() ], | ||
84 | MESSAGES: { | ||
85 | 'maxlength': $localize`A maximum of 5 tags can be used on a video.`, | ||
86 | 'arrayTagLength': $localize`A tag should be more than 2, and less than 30 characters long.` | ||
87 | } | ||
88 | } | ||
89 | |||
90 | this.VIDEO_SUPPORT = { | ||
91 | VALIDATORS: [ Validators.minLength(3), Validators.maxLength(1000) ], | ||
92 | MESSAGES: { | ||
93 | 'minlength': $localize`Video support must be at least 3 characters long.`, | ||
94 | 'maxlength': $localize`Video support cannot be more than 1000 characters long.` | ||
95 | } | ||
96 | } | ||
97 | |||
98 | this.VIDEO_SCHEDULE_PUBLICATION_AT = { | ||
99 | VALIDATORS: [ ], | ||
100 | MESSAGES: { | ||
101 | 'required': $localize`A date is required to schedule video update.` | ||
102 | } | ||
103 | } | ||
104 | |||
105 | this.VIDEO_ORIGINALLY_PUBLISHED_AT = { | ||
106 | VALIDATORS: [ ], | ||
107 | MESSAGES: {} | ||
108 | } | ||
109 | } | ||
110 | |||
111 | arrayTagLengthValidator (min = 2, max = 30): ValidatorFn { | ||
112 | return (control: AbstractControl): ValidationErrors => { | ||
113 | const array = control.value as Array<string> | ||
114 | |||
115 | if (array.every(e => e.length > min && e.length < max)) { | ||
116 | return null | ||
117 | } | ||
118 | |||
119 | return { 'arrayTagLength': true } | ||
120 | } | ||
121 | } | ||
122 | } | ||
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts index 747df65cf..b2c7fa9ba 100644 --- a/client/src/app/shared/shared-forms/index.ts +++ b/client/src/app/shared/shared-forms/index.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export * from './form-validators' | 1 | export * from './form-validator.service' |
2 | export * from './form-reactive' | 2 | export * from './form-reactive' |
3 | export * from './select' | 3 | export * from './select' |
4 | export * from './input-readonly-copy.component' | 4 | export * from './input-readonly-copy.component' |
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts index 0e0ed5bab..a28988f87 100644 --- a/client/src/app/shared/shared-forms/shared-form.module.ts +++ b/client/src/app/shared/shared-forms/shared-form.module.ts | |||
@@ -1,37 +1,21 @@ | |||
1 | 1 | ||
2 | import { NgModule } from '@angular/core' | ||
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||
4 | import { InputMaskModule } from 'primeng/inputmask' | 2 | import { InputMaskModule } from 'primeng/inputmask' |
5 | import { InputSwitchModule } from 'primeng/inputswitch' | 3 | import { InputSwitchModule } from 'primeng/inputswitch' |
4 | import { NgModule } from '@angular/core' | ||
5 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||
6 | import { NgSelectModule } from '@ng-select/ng-select' | 6 | import { NgSelectModule } from '@ng-select/ng-select' |
7 | import { BatchDomainsValidatorsService } from '@app/shared/shared-forms/form-validators/batch-domains-validators.service' | ||
8 | import { SharedGlobalIconModule } from '../shared-icons' | 7 | import { SharedGlobalIconModule } from '../shared-icons' |
9 | import { SharedMainModule } from '../shared-main/shared-main.module' | 8 | import { SharedMainModule } from '../shared-main/shared-main.module' |
10 | import { | 9 | import { FormValidatorService } from './form-validator.service' |
11 | CustomConfigValidatorsService, | ||
12 | FormValidatorService, | ||
13 | InstanceValidatorsService, | ||
14 | LoginValidatorsService, | ||
15 | ResetPasswordValidatorsService, | ||
16 | UserValidatorsService, | ||
17 | AbuseValidatorsService, | ||
18 | VideoAcceptOwnershipValidatorsService, | ||
19 | VideoBlockValidatorsService, | ||
20 | VideoCaptionsValidatorsService, | ||
21 | VideoChangeOwnershipValidatorsService, | ||
22 | VideoChannelValidatorsService, | ||
23 | VideoCommentValidatorsService, | ||
24 | VideoPlaylistValidatorsService, | ||
25 | VideoValidatorsService | ||
26 | } from './form-validators' | ||
27 | import { InputReadonlyCopyComponent } from './input-readonly-copy.component' | 10 | import { InputReadonlyCopyComponent } from './input-readonly-copy.component' |
28 | import { MarkdownTextareaComponent } from './markdown-textarea.component' | 11 | import { MarkdownTextareaComponent } from './markdown-textarea.component' |
29 | import { PeertubeCheckboxComponent } from './peertube-checkbox.component' | 12 | import { PeertubeCheckboxComponent } from './peertube-checkbox.component' |
30 | import { PreviewUploadComponent } from './preview-upload.component' | 13 | import { PreviewUploadComponent } from './preview-upload.component' |
31 | import { ReactiveFileComponent } from './reactive-file.component' | 14 | import { ReactiveFileComponent } from './reactive-file.component' |
15 | import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select' | ||
32 | import { TextareaAutoResizeDirective } from './textarea-autoresize.directive' | 16 | import { TextareaAutoResizeDirective } from './textarea-autoresize.directive' |
33 | import { TimestampInputComponent } from './timestamp-input.component' | 17 | import { TimestampInputComponent } from './timestamp-input.component' |
34 | import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select' | 18 | import { DynamicFormFieldComponent } from './dynamic-form-field.component' |
35 | 19 | ||
36 | @NgModule({ | 20 | @NgModule({ |
37 | imports: [ | 21 | imports: [ |
@@ -58,7 +42,9 @@ import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent | |||
58 | SelectChannelComponent, | 42 | SelectChannelComponent, |
59 | SelectOptionsComponent, | 43 | SelectOptionsComponent, |
60 | SelectTagsComponent, | 44 | SelectTagsComponent, |
61 | SelectCheckboxComponent | 45 | SelectCheckboxComponent, |
46 | |||
47 | DynamicFormFieldComponent | ||
62 | ], | 48 | ], |
63 | 49 | ||
64 | exports: [ | 50 | exports: [ |
@@ -80,27 +66,13 @@ import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent | |||
80 | SelectChannelComponent, | 66 | SelectChannelComponent, |
81 | SelectOptionsComponent, | 67 | SelectOptionsComponent, |
82 | SelectTagsComponent, | 68 | SelectTagsComponent, |
83 | SelectCheckboxComponent | 69 | SelectCheckboxComponent, |
70 | |||
71 | DynamicFormFieldComponent | ||
84 | ], | 72 | ], |
85 | 73 | ||
86 | providers: [ | 74 | providers: [ |
87 | CustomConfigValidatorsService, | 75 | FormValidatorService |
88 | FormValidatorService, | ||
89 | LoginValidatorsService, | ||
90 | InstanceValidatorsService, | ||
91 | LoginValidatorsService, | ||
92 | ResetPasswordValidatorsService, | ||
93 | UserValidatorsService, | ||
94 | AbuseValidatorsService, | ||
95 | VideoAcceptOwnershipValidatorsService, | ||
96 | VideoBlockValidatorsService, | ||
97 | VideoCaptionsValidatorsService, | ||
98 | VideoChangeOwnershipValidatorsService, | ||
99 | VideoChannelValidatorsService, | ||
100 | VideoCommentValidatorsService, | ||
101 | VideoPlaylistValidatorsService, | ||
102 | VideoValidatorsService, | ||
103 | BatchDomainsValidatorsService | ||
104 | ] | 76 | ] |
105 | }) | 77 | }) |
106 | export class SharedFormModule { } | 78 | export class SharedFormModule { } |
diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.ts b/client/src/app/shared/shared-forms/timestamp-input.component.ts index 8d67a96ac..0ffd03d02 100644 --- a/client/src/app/shared/shared-forms/timestamp-input.component.ts +++ b/client/src/app/shared/shared-forms/timestamp-input.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core' | 1 | import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { secondsToTime, timeToInt } from '../../../assets/player/utils' | 3 | import { secondsToTime, timeToInt } from '../../../assets/player/utils' |
4 | 4 | ||
@@ -19,6 +19,8 @@ export class TimestampInputComponent implements ControlValueAccessor, OnInit { | |||
19 | @Input() timestamp: number | 19 | @Input() timestamp: number |
20 | @Input() disabled = false | 20 | @Input() disabled = false |
21 | 21 | ||
22 | @Output() inputBlur = new EventEmitter() | ||
23 | |||
22 | timestampString: string | 24 | timestampString: string |
23 | 25 | ||
24 | constructor (private changeDetector: ChangeDetectorRef) {} | 26 | constructor (private changeDetector: ChangeDetectorRef) {} |
@@ -57,5 +59,7 @@ export class TimestampInputComponent implements ControlValueAccessor, OnInit { | |||
57 | 59 | ||
58 | this.propagateChange(this.timestamp) | 60 | this.propagateChange(this.timestamp) |
59 | } | 61 | } |
62 | |||
63 | this.inputBlur.emit() | ||
60 | } | 64 | } |
61 | } | 65 | } |
diff --git a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts index f09c3d1fc..d2cf53227 100644 --- a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts +++ b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { fromEvent, Observable, Subscription } from 'rxjs' | ||
1 | import { distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators' | 2 | import { distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators' |
2 | import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' | 3 | import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' |
3 | import { fromEvent, Observable, Subscription } from 'rxjs' | ||
4 | 4 | ||
5 | @Directive({ | 5 | @Directive({ |
6 | selector: '[myInfiniteScroller]' | 6 | selector: '[myInfiniteScroller]' |
@@ -80,7 +80,9 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterConten | |||
80 | } | 80 | } |
81 | 81 | ||
82 | private getMaximumScroll () { | 82 | private getMaximumScroll () { |
83 | return this.container.scrollHeight - window.innerHeight | 83 | const elementHeight = this.onItself ? this.container.clientHeight : window.innerHeight |
84 | |||
85 | return this.container.scrollHeight - elementHeight | ||
84 | } | 86 | } |
85 | 87 | ||
86 | private hasScroll () { | 88 | private hasScroll () { |
diff --git a/client/src/app/shared/shared-main/video/video-edit.model.ts b/client/src/app/shared/shared-main/video/video-edit.model.ts index 6a529e052..757b686c0 100644 --- a/client/src/app/shared/shared-main/video/video-edit.model.ts +++ b/client/src/app/shared/shared-main/video/video-edit.model.ts | |||
@@ -25,6 +25,8 @@ export class VideoEdit implements VideoUpdate { | |||
25 | scheduleUpdate?: VideoScheduleUpdate | 25 | scheduleUpdate?: VideoScheduleUpdate |
26 | originallyPublishedAt?: Date | string | 26 | originallyPublishedAt?: Date | string |
27 | 27 | ||
28 | pluginData?: any | ||
29 | |||
28 | constructor ( | 30 | constructor ( |
29 | video?: Video & { | 31 | video?: Video & { |
30 | tags: string[], | 32 | tags: string[], |
@@ -55,10 +57,12 @@ export class VideoEdit implements VideoUpdate { | |||
55 | 57 | ||
56 | this.scheduleUpdate = video.scheduledUpdate | 58 | this.scheduleUpdate = video.scheduledUpdate |
57 | this.originallyPublishedAt = video.originallyPublishedAt ? new Date(video.originallyPublishedAt) : null | 59 | this.originallyPublishedAt = video.originallyPublishedAt ? new Date(video.originallyPublishedAt) : null |
60 | |||
61 | this.pluginData = video.pluginData | ||
58 | } | 62 | } |
59 | } | 63 | } |
60 | 64 | ||
61 | patch (values: { [ id: string ]: string }) { | 65 | patch (values: { [ id: string ]: any }) { |
62 | Object.keys(values).forEach((key) => { | 66 | Object.keys(values).forEach((key) => { |
63 | this[ key ] = values[ key ] | 67 | this[ key ] = values[ key ] |
64 | }) | 68 | }) |
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index 73f0198e2..0dca3da0d 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts | |||
@@ -84,6 +84,8 @@ export class Video implements VideoServerModel { | |||
84 | currentTime: number | 84 | currentTime: number |
85 | } | 85 | } |
86 | 86 | ||
87 | pluginData?: any | ||
88 | |||
87 | static buildClientUrl (videoUUID: string) { | 89 | static buildClientUrl (videoUUID: string) { |
88 | return '/videos/watch/' + videoUUID | 90 | return '/videos/watch/' + videoUUID |
89 | } | 91 | } |
@@ -152,6 +154,8 @@ export class Video implements VideoServerModel { | |||
152 | 154 | ||
153 | this.originInstanceHost = this.account.host | 155 | this.originInstanceHost = this.account.host |
154 | this.originInstanceUrl = 'https://' + this.originInstanceHost | 156 | this.originInstanceUrl = 'https://' + this.originInstanceHost |
157 | |||
158 | this.pluginData = hash.pluginData | ||
155 | } | 159 | } |
156 | 160 | ||
157 | isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { | 161 | isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { |
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 48aff82b4..8a688c8ed 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -96,6 +96,7 @@ export class VideoService implements VideosProvider { | |||
96 | downloadEnabled: video.downloadEnabled, | 96 | downloadEnabled: video.downloadEnabled, |
97 | thumbnailfile: video.thumbnailfile, | 97 | thumbnailfile: video.thumbnailfile, |
98 | previewfile: video.previewfile, | 98 | previewfile: video.previewfile, |
99 | pluginData: video.pluginData, | ||
99 | scheduleUpdate, | 100 | scheduleUpdate, |
100 | originallyPublishedAt | 101 | originallyPublishedAt |
101 | } | 102 | } |
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts index 7193ccb1b..6edbb6023 100644 --- a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts +++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { BatchDomainsValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 2 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 4 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
5 | import { DOMAINS_VALIDATOR, getNotEmptyHosts } from '../form-validators/batch-domains-validators' | ||
5 | 6 | ||
6 | @Component({ | 7 | @Component({ |
7 | selector: 'my-batch-domains-modal', | 8 | selector: 'my-batch-domains-modal', |
@@ -18,8 +19,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit { | |||
18 | 19 | ||
19 | constructor ( | 20 | constructor ( |
20 | protected formValidatorService: FormValidatorService, | 21 | protected formValidatorService: FormValidatorService, |
21 | private modalService: NgbModal, | 22 | private modalService: NgbModal |
22 | private batchDomainsValidatorsService: BatchDomainsValidatorsService | ||
23 | ) { | 23 | ) { |
24 | super() | 24 | super() |
25 | } | 25 | } |
@@ -28,7 +28,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit { | |||
28 | if (!this.action) this.action = $localize`Process domains` | 28 | if (!this.action) this.action = $localize`Process domains` |
29 | 29 | ||
30 | this.buildForm({ | 30 | this.buildForm({ |
31 | domains: this.batchDomainsValidatorsService.DOMAINS | 31 | domains: DOMAINS_VALIDATOR |
32 | }) | 32 | }) |
33 | } | 33 | } |
34 | 34 | ||
@@ -42,7 +42,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit { | |||
42 | 42 | ||
43 | submit () { | 43 | submit () { |
44 | this.domains.emit( | 44 | this.domains.emit( |
45 | this.batchDomainsValidatorsService.getNotEmptyHosts(this.form.controls['domains'].value) | 45 | getNotEmptyHosts(this.form.controls['domains'].value) |
46 | ) | 46 | ) |
47 | this.form.reset() | 47 | this.form.reset() |
48 | this.hide() | 48 | this.hide() |
diff --git a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts index 8ab2fe940..cc8875f77 100644 --- a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { mapValues, pickBy } from 'lodash-es' | 1 | import { mapValues, pickBy } from 'lodash-es' |
2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 4 | import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' |
5 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { Account } from '@app/shared/shared-main' | 6 | import { Account } from '@app/shared/shared-main' |
6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
@@ -28,7 +29,6 @@ export class AccountReportComponent extends FormReactive implements OnInit { | |||
28 | constructor ( | 29 | constructor ( |
29 | protected formValidatorService: FormValidatorService, | 30 | protected formValidatorService: FormValidatorService, |
30 | private modalService: NgbModal, | 31 | private modalService: NgbModal, |
31 | private abuseValidatorsService: AbuseValidatorsService, | ||
32 | private abuseService: AbuseService, | 32 | private abuseService: AbuseService, |
33 | private notifier: Notifier | 33 | private notifier: Notifier |
34 | ) { | 34 | ) { |
@@ -51,7 +51,7 @@ export class AccountReportComponent extends FormReactive implements OnInit { | |||
51 | this.modalTitle = $localize`Report ${this.account.displayName}` | 51 | this.modalTitle = $localize`Report ${this.account.displayName}` |
52 | 52 | ||
53 | this.buildForm({ | 53 | this.buildForm({ |
54 | reason: this.abuseValidatorsService.ABUSE_REASON, | 54 | reason: ABUSE_REASON_VALIDATOR, |
55 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) | 55 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) |
56 | }) | 56 | }) |
57 | 57 | ||
diff --git a/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts index d75f4d717..c7395c7b7 100644 --- a/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { mapValues, pickBy } from 'lodash-es' | 1 | import { mapValues, pickBy } from 'lodash-es' |
2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 4 | import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' |
5 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { VideoComment } from '@app/shared/shared-video-comment' | 6 | import { VideoComment } from '@app/shared/shared-video-comment' |
6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
@@ -28,7 +29,6 @@ export class CommentReportComponent extends FormReactive implements OnInit { | |||
28 | constructor ( | 29 | constructor ( |
29 | protected formValidatorService: FormValidatorService, | 30 | protected formValidatorService: FormValidatorService, |
30 | private modalService: NgbModal, | 31 | private modalService: NgbModal, |
31 | private abuseValidatorsService: AbuseValidatorsService, | ||
32 | private abuseService: AbuseService, | 32 | private abuseService: AbuseService, |
33 | private notifier: Notifier | 33 | private notifier: Notifier |
34 | ) { | 34 | ) { |
@@ -51,7 +51,7 @@ export class CommentReportComponent extends FormReactive implements OnInit { | |||
51 | this.modalTitle = $localize`Report comment` | 51 | this.modalTitle = $localize`Report comment` |
52 | 52 | ||
53 | this.buildForm({ | 53 | this.buildForm({ |
54 | reason: this.abuseValidatorsService.ABUSE_REASON, | 54 | reason: ABUSE_REASON_VALIDATOR, |
55 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) | 55 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null) |
56 | }) | 56 | }) |
57 | 57 | ||
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts index edff6d325..5b06c0bc7 100644 --- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts | |||
@@ -3,7 +3,8 @@ import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/uti | |||
3 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
4 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 4 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
5 | import { Notifier } from '@app/core' | 5 | import { Notifier } from '@app/core' |
6 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 6 | import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 8 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 9 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' | 10 | import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' |
@@ -30,7 +31,6 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
30 | constructor ( | 31 | constructor ( |
31 | protected formValidatorService: FormValidatorService, | 32 | protected formValidatorService: FormValidatorService, |
32 | private modalService: NgbModal, | 33 | private modalService: NgbModal, |
33 | private abuseValidatorsService: AbuseValidatorsService, | ||
34 | private abuseService: AbuseService, | 34 | private abuseService: AbuseService, |
35 | private notifier: Notifier, | 35 | private notifier: Notifier, |
36 | private sanitizer: DomSanitizer | 36 | private sanitizer: DomSanitizer |
@@ -68,7 +68,7 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
68 | 68 | ||
69 | ngOnInit () { | 69 | ngOnInit () { |
70 | this.buildForm({ | 70 | this.buildForm({ |
71 | reason: this.abuseValidatorsService.ABUSE_REASON, | 71 | reason: ABUSE_REASON_VALIDATOR, |
72 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null), | 72 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null), |
73 | timestamp: { | 73 | timestamp: { |
74 | hasStart: null, | 74 | hasStart: null, |
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts index f9a0381c5..afc69a1b8 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier, UserService } from '@app/core' | 2 | import { Notifier, UserService } from '@app/core' |
3 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
6 | import { User } from '@shared/models' | 6 | import { User } from '@shared/models' |
7 | import { USER_BAN_REASON_VALIDATOR } from '../form-validators/user-validators' | ||
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-user-ban-modal', | 10 | selector: 'my-user-ban-modal', |
@@ -21,15 +22,14 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
21 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
22 | private modalService: NgbModal, | 23 | private modalService: NgbModal, |
23 | private notifier: Notifier, | 24 | private notifier: Notifier, |
24 | private userService: UserService, | 25 | private userService: UserService |
25 | private userValidatorsService: UserValidatorsService | ||
26 | ) { | 26 | ) { |
27 | super() | 27 | super() |
28 | } | 28 | } |
29 | 29 | ||
30 | ngOnInit () { | 30 | ngOnInit () { |
31 | this.buildForm({ | 31 | this.buildForm({ |
32 | reason: this.userValidatorsService.USER_BAN_REASON | 32 | reason: USER_BAN_REASON_VALIDATOR |
33 | }) | 33 | }) |
34 | } | 34 | } |
35 | 35 | ||
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts index 2bef9efdd..fb47989dc 100644 --- a/client/src/app/shared/shared-moderation/video-block.component.ts +++ b/client/src/app/shared/shared-moderation/video-block.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, FormValidatorService, VideoBlockValidatorsService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { Video } from '@app/shared/shared-main' | 4 | import { Video } from '@app/shared/shared-main' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
7 | import { VIDEO_BLOCK_REASON_VALIDATOR } from '../form-validators/video-block-validators' | ||
7 | import { VideoBlockService } from './video-block.service' | 8 | import { VideoBlockService } from './video-block.service' |
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
@@ -25,7 +26,6 @@ export class VideoBlockComponent extends FormReactive implements OnInit { | |||
25 | constructor ( | 26 | constructor ( |
26 | protected formValidatorService: FormValidatorService, | 27 | protected formValidatorService: FormValidatorService, |
27 | private modalService: NgbModal, | 28 | private modalService: NgbModal, |
28 | private videoBlockValidatorsService: VideoBlockValidatorsService, | ||
29 | private videoBlocklistService: VideoBlockService, | 29 | private videoBlocklistService: VideoBlockService, |
30 | private notifier: Notifier | 30 | private notifier: Notifier |
31 | ) { | 31 | ) { |
@@ -36,7 +36,7 @@ export class VideoBlockComponent extends FormReactive implements OnInit { | |||
36 | const defaultValues = { unfederate: 'true' } | 36 | const defaultValues = { unfederate: 'true' } |
37 | 37 | ||
38 | this.buildForm({ | 38 | this.buildForm({ |
39 | reason: this.videoBlockValidatorsService.VIDEO_BLOCK_REASON, | 39 | reason: VIDEO_BLOCK_REASON_VALIDATOR, |
40 | unfederate: null | 40 | unfederate: null |
41 | }, defaultValues) | 41 | }, defaultValues) |
42 | } | 42 | } |
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts index 8d8e8a3a5..f57a50770 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.ts +++ b/client/src/app/shared/shared-share-modal/video-share.component.ts | |||
@@ -37,6 +37,7 @@ export class VideoShareComponent { | |||
37 | @Input() video: VideoDetails = null | 37 | @Input() video: VideoDetails = null |
38 | @Input() videoCaptions: VideoCaption[] = [] | 38 | @Input() videoCaptions: VideoCaption[] = [] |
39 | @Input() playlist: VideoPlaylist = null | 39 | @Input() playlist: VideoPlaylist = null |
40 | @Input() playlistPosition: number = null | ||
40 | 41 | ||
41 | activeVideoId: TabId = 'url' | 42 | activeVideoId: TabId = 'url' |
42 | activePlaylistId: TabId = 'url' | 43 | activePlaylistId: TabId = 'url' |
@@ -45,8 +46,6 @@ export class VideoShareComponent { | |||
45 | isAdvancedCustomizationCollapsed = true | 46 | isAdvancedCustomizationCollapsed = true |
46 | includeVideoInPlaylist = false | 47 | includeVideoInPlaylist = false |
47 | 48 | ||
48 | private playlistPosition: number = null | ||
49 | |||
50 | constructor (private modalService: NgbModal) { } | 49 | constructor (private modalService: NgbModal) { } |
51 | 50 | ||
52 | show (currentVideoTimestamp?: number, currentPlaylistPosition?: number) { | 51 | show (currentVideoTimestamp?: number, currentPlaylistPosition?: number) { |
@@ -107,7 +106,7 @@ export class VideoShareComponent { | |||
107 | 106 | ||
108 | if (!this.includeVideoInPlaylist) return base | 107 | if (!this.includeVideoInPlaylist) return base |
109 | 108 | ||
110 | return base + '?videoId=' + this.video.uuid | 109 | return base + '?playlistPosition=' + this.playlistPosition |
111 | } | 110 | } |
112 | 111 | ||
113 | notSecure () { | 112 | notSecure () { |
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts index ab77f6f9c..2497e001c 100644 --- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts | |||
@@ -95,27 +95,22 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit, | |||
95 | const autoPlayVideo = this.form.value['autoPlayVideo'] | 95 | const autoPlayVideo = this.form.value['autoPlayVideo'] |
96 | const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] | 96 | const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] |
97 | 97 | ||
98 | let videoLanguages: string[] = this.form.value['videoLanguages'] | 98 | const videoLanguagesForm = this.form.value['videoLanguages'] |
99 | 99 | ||
100 | if (Array.isArray(videoLanguages)) { | 100 | if (Array.isArray(videoLanguagesForm)) { |
101 | if (videoLanguages.length > 20) { | 101 | if (videoLanguagesForm.length > 20) { |
102 | this.notifier.error($localize`Too many languages are enabled. Please enable them all or stay below 20 enabled languages.`) | 102 | this.notifier.error($localize`Too many languages are enabled. Please enable them all or stay below 20 enabled languages.`) |
103 | return | 103 | return |
104 | } | 104 | } |
105 | 105 | ||
106 | if (videoLanguages.length === 0) { | 106 | if (videoLanguagesForm.length === 0) { |
107 | this.notifier.error($localize`You need to enable at least 1 video language.`) | 107 | this.notifier.error($localize`You need to enable at least 1 video language.`) |
108 | return | 108 | return |
109 | } | 109 | } |
110 | |||
111 | if ( | ||
112 | videoLanguages.length === this.languageItems.length || | ||
113 | (videoLanguages.length === 1 && videoLanguages[0] === this.allLanguagesGroup) | ||
114 | ) { | ||
115 | videoLanguages = null // null means "All" | ||
116 | } | ||
117 | } | 110 | } |
118 | 111 | ||
112 | const videoLanguages = this.getVideoLanguages(videoLanguagesForm) | ||
113 | |||
119 | let details: UserUpdateMe = { | 114 | let details: UserUpdateMe = { |
120 | nsfwPolicy, | 115 | nsfwPolicy, |
121 | webTorrentEnabled, | 116 | webTorrentEnabled, |
@@ -124,6 +119,10 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit, | |||
124 | videoLanguages | 119 | videoLanguages |
125 | } | 120 | } |
126 | 121 | ||
122 | if (videoLanguages) { | ||
123 | details = Object.assign(details, videoLanguages) | ||
124 | } | ||
125 | |||
127 | if (onlyKeys) details = pick(details, onlyKeys) | 126 | if (onlyKeys) details = pick(details, onlyKeys) |
128 | 127 | ||
129 | if (this.authService.isLoggedIn()) { | 128 | if (this.authService.isLoggedIn()) { |
@@ -141,4 +140,29 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit, | |||
141 | if (this.notifyOnUpdate) this.notifier.success($localize`Display/Video settings updated.`) | 140 | if (this.notifyOnUpdate) this.notifier.success($localize`Display/Video settings updated.`) |
142 | } | 141 | } |
143 | } | 142 | } |
143 | |||
144 | private getVideoLanguages (videoLanguages: ItemSelectCheckboxValue[]) { | ||
145 | if (!Array.isArray(videoLanguages)) return undefined | ||
146 | |||
147 | // null means "All" | ||
148 | if (videoLanguages.length === this.languageItems.length) return null | ||
149 | |||
150 | if (videoLanguages.length === 1) { | ||
151 | const videoLanguage = videoLanguages[0] | ||
152 | |||
153 | if (typeof videoLanguage === 'string') { | ||
154 | if (videoLanguage === this.allLanguagesGroup) return null | ||
155 | } else { | ||
156 | if (videoLanguage.group === this.allLanguagesGroup) return null | ||
157 | } | ||
158 | } | ||
159 | |||
160 | return videoLanguages.map(l => { | ||
161 | if (typeof l === 'string') return l | ||
162 | |||
163 | if (l.group) return l.group | ||
164 | |||
165 | return l.id + '' | ||
166 | }) | ||
167 | } | ||
144 | } | 168 | } |
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts index 286ecac02..b46c91bf8 100644 --- a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts +++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' | 2 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
3 | import { USER_EMAIL_VALIDATOR } from '../form-validators/user-validators' | ||
3 | 4 | ||
4 | @Component({ | 5 | @Component({ |
5 | selector: 'my-remote-subscribe', | 6 | selector: 'my-remote-subscribe', |
@@ -12,15 +13,14 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit { | |||
12 | @Input() showHelp = false | 13 | @Input() showHelp = false |
13 | 14 | ||
14 | constructor ( | 15 | constructor ( |
15 | protected formValidatorService: FormValidatorService, | 16 | protected formValidatorService: FormValidatorService |
16 | private userValidatorsService: UserValidatorsService | ||
17 | ) { | 17 | ) { |
18 | super() | 18 | super() |
19 | } | 19 | } |
20 | 20 | ||
21 | ngOnInit () { | 21 | ngOnInit () { |
22 | this.buildForm({ | 22 | this.buildForm({ |
23 | text: this.userValidatorsService.USER_EMAIL | 23 | text: USER_EMAIL_VALIDATOR |
24 | }) | 24 | }) |
25 | } | 25 | } |
26 | 26 | ||
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html index a40e0699e..37d5017cf 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html | |||
@@ -2,58 +2,60 @@ | |||
2 | <div class="header"> | 2 | <div class="header"> |
3 | <div class="first-row"> | 3 | <div class="first-row"> |
4 | <div i18n class="title">Save to</div> | 4 | <div i18n class="title">Save to</div> |
5 | |||
6 | <div class="options" (click)="displayOptions = !displayOptions"> | ||
7 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> | ||
8 | |||
9 | <span i18n>Options</span> | ||
10 | </div> | ||
11 | </div> | 5 | </div> |
6 | </div> | ||
12 | 7 | ||
13 | <div class="options-row" *ngIf="displayOptions"> | 8 | <div class="input-container"> |
14 | <div> | 9 | <input type="text" placeholder="Search playlists" i18n-placeholder [(ngModel)]="videoPlaylistSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" /> |
15 | <my-peertube-checkbox | 10 | </div> |
16 | inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled" | ||
17 | i18n-labelText labelText="Start at" | ||
18 | ></my-peertube-checkbox> | ||
19 | |||
20 | <my-timestamp-input | ||
21 | [timestamp]="timestampOptions.startTimestamp" | ||
22 | [maxTimestamp]="video.duration" | ||
23 | [disabled]="!timestampOptions.startTimestampEnabled" | ||
24 | [(ngModel)]="timestampOptions.startTimestamp" | ||
25 | ></my-timestamp-input> | ||
26 | </div> | ||
27 | 11 | ||
28 | <div> | 12 | <div class="playlists"> |
13 | <div | ||
14 | *ngFor="let playlist of videoPlaylists" | ||
15 | class="playlist dropdown-item" [ngClass]="{ 'has-optional-row': playlist.optionalRowDisplayed }" | ||
16 | > | ||
17 | <div class="primary-row"> | ||
29 | <my-peertube-checkbox | 18 | <my-peertube-checkbox |
30 | inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled" | 19 | [disabled]="isPresentMultipleTimes(playlist) || playlist.optionalRowDisplayed" [inputName]="getPrimaryInputName(playlist)" |
31 | i18n-labelText labelText="Stop at" | 20 | [ngModel]="isPrimaryCheckboxChecked(playlist)" [onPushWorkaround]="true" |
21 | (click)="toggleMainPlaylist($event, playlist)" | ||
32 | ></my-peertube-checkbox> | 22 | ></my-peertube-checkbox> |
33 | 23 | ||
34 | <my-timestamp-input | 24 | <label class="display-name" (click)="toggleMainPlaylist($event, playlist)"> |
35 | [timestamp]="timestampOptions.stopTimestamp" | 25 | {{ playlist.displayName }} |
36 | [maxTimestamp]="video.duration" | 26 | </label> |
37 | [disabled]="!timestampOptions.stopTimestampEnabled" | 27 | |
38 | [(ngModel)]="timestampOptions.stopTimestamp" | 28 | <div class="optional-row-icon" *ngIf="isPrimaryCheckboxChecked(playlist)" (click)="toggleOptionalRow(playlist)"> |
39 | ></my-timestamp-input> | 29 | <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> |
30 | </div> | ||
40 | </div> | 31 | </div> |
41 | </div> | ||
42 | </div> | ||
43 | 32 | ||
44 | <div class="input-container"> | 33 | <div class="optional-rows" *ngIf="playlist.optionalRowDisplayed"> |
45 | <input type="text" placeholder="Search playlists" i18n-placeholder [(ngModel)]="videoPlaylistSearch" (ngModelChange)="onVideoPlaylistSearchChanged()" /> | 34 | <div class="labels"> |
46 | </div> | 35 | <div i18n>Start at</div> |
36 | <div i18n>Stop at</div> | ||
37 | </div> | ||
47 | 38 | ||
48 | <div class="playlists"> | 39 | <div *ngFor="let element of buildOptionalRowElements(playlist)"> |
49 | <div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)"> | 40 | <my-peertube-checkbox |
50 | <my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist" [onPushWorkaround]="true"></my-peertube-checkbox> | 41 | [inputName]="getOptionalInputName(playlist, element)" |
42 | [ngModel]="element.enabled" [onPushWorkaround]="true" | ||
43 | (click)="toggleOptionalPlaylist($event, playlist, element, startAt.timestamp, stopAt.timestamp)" | ||
44 | ></my-peertube-checkbox> | ||
51 | 45 | ||
52 | <div class="display-name"> | 46 | <my-timestamp-input |
53 | {{ playlist.displayName }} | 47 | [maxTimestamp]="video.duration" |
48 | [(ngModel)]="element.startTimestamp" | ||
49 | (inputBlur)="onElementTimestampUpdate(playlist, element)" | ||
50 | #startAt | ||
51 | ></my-timestamp-input> | ||
54 | 52 | ||
55 | <div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info"> | 53 | <my-timestamp-input |
56 | {{ formatTimestamp(playlist) }} | 54 | [maxTimestamp]="video.duration" |
55 | [(ngModel)]="element.stopTimestamp" | ||
56 | (inputBlur)="onElementTimestampUpdate(playlist, element)" | ||
57 | #stopAt | ||
58 | ></my-timestamp-input> | ||
57 | </div> | 59 | </div> |
58 | </div> | 60 | </div> |
59 | </div> | 61 | </div> |
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss index 47baa997b..d2c8804e3 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss | |||
@@ -1,12 +1,20 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | $optional-rows-checkbox-width: 34px; | ||
5 | $timestamp-width: 50px; | ||
6 | $timestamp-margin-right: 10px; | ||
7 | |||
4 | .header, | 8 | .header, |
5 | .dropdown-item, | 9 | .dropdown-item, |
6 | .input-container { | 10 | .input-container { |
7 | padding: 8px 24px; | 11 | padding: 8px 24px; |
8 | } | 12 | } |
9 | 13 | ||
14 | .dropdown-item:active { | ||
15 | color: inherit; | ||
16 | } | ||
17 | |||
10 | .header { | 18 | .header { |
11 | min-width: 240px; | 19 | min-width: 240px; |
12 | margin-bottom: 10px; | 20 | margin-bottom: 10px; |
@@ -20,31 +28,6 @@ | |||
20 | font-size: 18px; | 28 | font-size: 18px; |
21 | flex-grow: 1; | 29 | flex-grow: 1; |
22 | } | 30 | } |
23 | |||
24 | .options { | ||
25 | display: flex; | ||
26 | align-items: center; | ||
27 | font-size: 14px; | ||
28 | cursor: pointer; | ||
29 | |||
30 | my-global-icon { | ||
31 | @include apply-svg-color(#333); | ||
32 | |||
33 | width: 16px; | ||
34 | height: 23px; | ||
35 | margin-right: 3px; | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | .options-row { | ||
41 | margin-top: 10px; | ||
42 | padding-left: 10px; | ||
43 | |||
44 | > div { | ||
45 | display: flex; | ||
46 | align-items: center; | ||
47 | } | ||
48 | } | 31 | } |
49 | } | 32 | } |
50 | 33 | ||
@@ -54,8 +37,16 @@ | |||
54 | } | 37 | } |
55 | 38 | ||
56 | .playlist { | 39 | .playlist { |
57 | display: inline-flex; | 40 | padding: 8px 10px 8px 24px; |
58 | cursor: pointer; | 41 | |
42 | &.has-optional-row:hover { | ||
43 | background-color: inherit; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | .primary-row, | ||
48 | .optional-rows > div { | ||
49 | display: flex; | ||
59 | 50 | ||
60 | my-peertube-checkbox { | 51 | my-peertube-checkbox { |
61 | margin-right: 10px; | 52 | margin-right: 10px; |
@@ -65,11 +56,58 @@ | |||
65 | .display-name { | 56 | .display-name { |
66 | display: flex; | 57 | display: flex; |
67 | align-items: flex-end; | 58 | align-items: flex-end; |
59 | flex-grow: 1; | ||
60 | margin: 0; | ||
61 | font-weight: $font-regular; | ||
62 | cursor: pointer; | ||
63 | } | ||
64 | |||
65 | .optional-row-icon { | ||
66 | display: flex; | ||
67 | align-items: center; | ||
68 | font-size: 14px; | ||
69 | cursor: pointer; | ||
70 | |||
71 | my-global-icon { | ||
72 | @include apply-svg-color(#333); | ||
73 | |||
74 | width: 19px; | ||
75 | height: 19px; | ||
76 | margin-right: 0; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | my-timestamp-input { | ||
81 | margin-right: $timestamp-margin-right; | ||
82 | |||
83 | ::ng-deep .ui-inputtext { | ||
84 | padding: 0; | ||
85 | width: $timestamp-width; | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | .optional-rows { | ||
91 | > div { | ||
92 | padding: 8px 5px 5px 10px; | ||
93 | } | ||
94 | |||
95 | my-peertube-checkbox { | ||
96 | display: block; | ||
97 | width: $optional-rows-checkbox-width; | ||
98 | margin-right: 0 !important; | ||
99 | } | ||
100 | |||
101 | .labels { | ||
102 | margin-left: $optional-rows-checkbox-width; | ||
103 | font-size: 13px; | ||
104 | color: pvar(--greyForegroundColor); | ||
105 | padding-top: 5px; | ||
106 | padding-bottom: 0; | ||
68 | 107 | ||
69 | .timestamp-info { | 108 | div { |
70 | font-size: 0.9em; | 109 | margin-right: $timestamp-margin-right; |
71 | color: pvar(--greyForegroundColor); | 110 | width: $timestamp-width; |
72 | margin-left: 5px; | ||
73 | } | 111 | } |
74 | } | 112 | } |
75 | } | 113 | } |
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts index 757ffa099..681e5becd 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts | |||
@@ -3,21 +3,34 @@ import { Subject, Subscription } from 'rxjs' | |||
3 | import { debounceTime, filter } from 'rxjs/operators' | 3 | import { debounceTime, filter } from 'rxjs/operators' |
4 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' | 4 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' |
5 | import { AuthService, DisableForReuseHook, Notifier } from '@app/core' | 5 | import { AuthService, DisableForReuseHook, Notifier } from '@app/core' |
6 | import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/shared-forms' | 6 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
7 | import { Video, VideoExistInPlaylist, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models' | 7 | import { |
8 | Video, | ||
9 | VideoExistInPlaylist, | ||
10 | VideoPlaylistCreate, | ||
11 | VideoPlaylistElementCreate, | ||
12 | VideoPlaylistElementUpdate, | ||
13 | VideoPlaylistPrivacy | ||
14 | } from '@shared/models' | ||
8 | import { secondsToTime } from '../../../assets/player/utils' | 15 | import { secondsToTime } from '../../../assets/player/utils' |
16 | import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators' | ||
9 | import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service' | 17 | import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service' |
10 | 18 | ||
11 | const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') | 19 | const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') |
12 | 20 | ||
21 | type PlaylistElement = { | ||
22 | enabled: boolean | ||
23 | playlistElementId?: number | ||
24 | startTimestamp?: number | ||
25 | stopTimestamp?: number | ||
26 | } | ||
27 | |||
13 | type PlaylistSummary = { | 28 | type PlaylistSummary = { |
14 | id: number | 29 | id: number |
15 | inPlaylist: boolean | ||
16 | displayName: string | 30 | displayName: string |
31 | optionalRowDisplayed: boolean | ||
17 | 32 | ||
18 | playlistElementId?: number | 33 | elements: PlaylistElement[] |
19 | startTimestamp?: number | ||
20 | stopTimestamp?: number | ||
21 | } | 34 | } |
22 | 35 | ||
23 | @Component({ | 36 | @Component({ |
@@ -32,16 +45,11 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
32 | @Input() lazyLoad = false | 45 | @Input() lazyLoad = false |
33 | 46 | ||
34 | isNewPlaylistBlockOpened = false | 47 | isNewPlaylistBlockOpened = false |
48 | |||
35 | videoPlaylistSearch: string | 49 | videoPlaylistSearch: string |
36 | videoPlaylistSearchChanged = new Subject<string>() | 50 | videoPlaylistSearchChanged = new Subject<string>() |
51 | |||
37 | videoPlaylists: PlaylistSummary[] = [] | 52 | videoPlaylists: PlaylistSummary[] = [] |
38 | timestampOptions: { | ||
39 | startTimestampEnabled: boolean | ||
40 | startTimestamp: number | ||
41 | stopTimestampEnabled: boolean | ||
42 | stopTimestamp: number | ||
43 | } | ||
44 | displayOptions = false | ||
45 | 53 | ||
46 | private disabled = false | 54 | private disabled = false |
47 | 55 | ||
@@ -53,7 +61,6 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
53 | private authService: AuthService, | 61 | private authService: AuthService, |
54 | private notifier: Notifier, | 62 | private notifier: Notifier, |
55 | private videoPlaylistService: VideoPlaylistService, | 63 | private videoPlaylistService: VideoPlaylistService, |
56 | private videoPlaylistValidatorsService: VideoPlaylistValidatorsService, | ||
57 | private cd: ChangeDetectorRef | 64 | private cd: ChangeDetectorRef |
58 | ) { | 65 | ) { |
59 | super() | 66 | super() |
@@ -65,7 +72,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
65 | 72 | ||
66 | ngOnInit () { | 73 | ngOnInit () { |
67 | this.buildForm({ | 74 | this.buildForm({ |
68 | displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME | 75 | displayName: VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR |
69 | }) | 76 | }) |
70 | 77 | ||
71 | this.videoPlaylistService.listenToMyAccountPlaylistsChange() | 78 | this.videoPlaylistService.listenToMyAccountPlaylistsChange() |
@@ -106,7 +113,6 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
106 | this.videoPlaylists = [] | 113 | this.videoPlaylists = [] |
107 | this.videoPlaylistSearch = undefined | 114 | this.videoPlaylistSearch = undefined |
108 | 115 | ||
109 | this.resetOptions(true) | ||
110 | this.load() | 116 | this.load() |
111 | 117 | ||
112 | this.cd.markForCheck() | 118 | this.cd.markForCheck() |
@@ -115,7 +121,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
115 | load () { | 121 | load () { |
116 | logger('Loading component') | 122 | logger('Loading component') |
117 | 123 | ||
118 | this.listenToPlaylistChanges() | 124 | this.listenToVideoPlaylistChange() |
119 | 125 | ||
120 | this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch) | 126 | this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch) |
121 | .subscribe(playlistsResult => { | 127 | .subscribe(playlistsResult => { |
@@ -128,7 +134,6 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
128 | openChange (opened: boolean) { | 134 | openChange (opened: boolean) { |
129 | if (opened === false) { | 135 | if (opened === false) { |
130 | this.isNewPlaylistBlockOpened = false | 136 | this.isNewPlaylistBlockOpened = false |
131 | this.displayOptions = false | ||
132 | } | 137 | } |
133 | } | 138 | } |
134 | 139 | ||
@@ -138,17 +143,49 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
138 | this.isNewPlaylistBlockOpened = true | 143 | this.isNewPlaylistBlockOpened = true |
139 | } | 144 | } |
140 | 145 | ||
141 | togglePlaylist (event: Event, playlist: PlaylistSummary) { | 146 | toggleMainPlaylist (e: Event, playlist: PlaylistSummary) { |
142 | event.preventDefault() | 147 | e.preventDefault() |
148 | |||
149 | if (this.isPresentMultipleTimes(playlist) || playlist.optionalRowDisplayed) return | ||
143 | 150 | ||
144 | if (playlist.inPlaylist === true) { | 151 | if (playlist.elements.length === 0) { |
145 | this.removeVideoFromPlaylist(playlist) | 152 | const element: PlaylistElement = { |
153 | enabled: true, | ||
154 | playlistElementId: undefined, | ||
155 | startTimestamp: 0, | ||
156 | stopTimestamp: this.video.duration | ||
157 | } | ||
158 | |||
159 | this.addVideoInPlaylist(playlist, element) | ||
146 | } else { | 160 | } else { |
147 | this.addVideoInPlaylist(playlist) | 161 | this.removeVideoFromPlaylist(playlist, playlist.elements[0].playlistElementId) |
162 | playlist.elements = [] | ||
148 | } | 163 | } |
149 | 164 | ||
150 | playlist.inPlaylist = !playlist.inPlaylist | 165 | this.cd.markForCheck() |
151 | this.resetOptions() | 166 | } |
167 | |||
168 | toggleOptionalPlaylist (e: Event, playlist: PlaylistSummary, element: PlaylistElement, startTimestamp: number, stopTimestamp: number) { | ||
169 | e.preventDefault() | ||
170 | |||
171 | if (element.enabled) { | ||
172 | this.removeVideoFromPlaylist(playlist, element.playlistElementId) | ||
173 | element.enabled = false | ||
174 | |||
175 | // Hide optional rows pane when the user unchecked all the playlists | ||
176 | if (this.isPrimaryCheckboxChecked(playlist) === false) { | ||
177 | playlist.optionalRowDisplayed = false | ||
178 | } | ||
179 | } else { | ||
180 | const element: PlaylistElement = { | ||
181 | enabled: true, | ||
182 | playlistElementId: undefined, | ||
183 | startTimestamp, | ||
184 | stopTimestamp | ||
185 | } | ||
186 | |||
187 | this.addVideoInPlaylist(playlist, element) | ||
188 | } | ||
152 | 189 | ||
153 | this.cd.markForCheck() | 190 | this.cd.markForCheck() |
154 | } | 191 | } |
@@ -172,34 +209,99 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
172 | ) | 209 | ) |
173 | } | 210 | } |
174 | 211 | ||
175 | resetOptions (resetTimestamp = false) { | 212 | onVideoPlaylistSearchChanged () { |
176 | this.displayOptions = false | 213 | this.videoPlaylistSearchChanged.next() |
214 | } | ||
177 | 215 | ||
178 | this.timestampOptions = {} as any | 216 | isPrimaryCheckboxChecked (playlist: PlaylistSummary) { |
179 | this.timestampOptions.startTimestampEnabled = false | 217 | return playlist.elements.filter(e => e.enabled) |
180 | this.timestampOptions.stopTimestampEnabled = false | 218 | .length !== 0 |
219 | } | ||
181 | 220 | ||
182 | if (resetTimestamp) { | 221 | toggleOptionalRow (playlist: PlaylistSummary) { |
183 | this.timestampOptions.startTimestamp = 0 | 222 | playlist.optionalRowDisplayed = !playlist.optionalRowDisplayed |
184 | this.timestampOptions.stopTimestamp = this.video.duration | 223 | |
185 | } | 224 | this.cd.markForCheck() |
186 | } | 225 | } |
187 | 226 | ||
188 | formatTimestamp (playlist: PlaylistSummary) { | 227 | getPrimaryInputName (playlist: PlaylistSummary) { |
189 | const start = playlist.startTimestamp ? secondsToTime(playlist.startTimestamp) : '' | 228 | return 'in-playlist-primary-' + playlist.id |
190 | const stop = playlist.stopTimestamp ? secondsToTime(playlist.stopTimestamp) : '' | 229 | } |
191 | 230 | ||
192 | return `(${start}-${stop})` | 231 | getOptionalInputName (playlist: PlaylistSummary, element?: PlaylistElement) { |
232 | const suffix = element | ||
233 | ? '-' + element.playlistElementId | ||
234 | : '' | ||
235 | |||
236 | return 'in-playlist-optional-' + playlist.id + suffix | ||
193 | } | 237 | } |
194 | 238 | ||
195 | onVideoPlaylistSearchChanged () { | 239 | buildOptionalRowElements (playlist: PlaylistSummary) { |
196 | this.videoPlaylistSearchChanged.next() | 240 | const elements = playlist.elements |
241 | |||
242 | const lastElement = elements.length === 0 | ||
243 | ? undefined | ||
244 | : elements[elements.length - 1] | ||
245 | |||
246 | // Build an empty last element | ||
247 | if (!lastElement || lastElement.enabled === true) { | ||
248 | elements.push({ | ||
249 | enabled: false, | ||
250 | startTimestamp: 0, | ||
251 | stopTimestamp: this.video.duration | ||
252 | }) | ||
253 | } | ||
254 | |||
255 | return elements | ||
256 | } | ||
257 | |||
258 | isPresentMultipleTimes (playlist: PlaylistSummary) { | ||
259 | return playlist.elements.filter(e => e.enabled === true).length > 1 | ||
260 | } | ||
261 | |||
262 | onElementTimestampUpdate (playlist: PlaylistSummary, element: PlaylistElement) { | ||
263 | if (!element.playlistElementId || element.enabled === false) return | ||
264 | |||
265 | const body: VideoPlaylistElementUpdate = { | ||
266 | startTimestamp: element.startTimestamp, | ||
267 | stopTimestamp: element.stopTimestamp | ||
268 | } | ||
269 | |||
270 | this.videoPlaylistService.updateVideoOfPlaylist(playlist.id, element.playlistElementId, body, this.video.id) | ||
271 | .subscribe( | ||
272 | () => { | ||
273 | this.notifier.success($localize`Timestamps updated`) | ||
274 | }, | ||
275 | |||
276 | err => { | ||
277 | this.notifier.error(err.message) | ||
278 | }, | ||
279 | |||
280 | () => this.cd.markForCheck() | ||
281 | ) | ||
197 | } | 282 | } |
198 | 283 | ||
199 | private removeVideoFromPlaylist (playlist: PlaylistSummary) { | 284 | private isOptionalRowDisplayed (playlist: PlaylistSummary) { |
200 | if (!playlist.playlistElementId) return | 285 | const elements = playlist.elements.filter(e => e.enabled) |
286 | |||
287 | if (elements.length > 1) return true | ||
201 | 288 | ||
202 | this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, playlist.playlistElementId, this.video.id) | 289 | if (elements.length === 1) { |
290 | const element = elements[0] | ||
291 | |||
292 | if ( | ||
293 | (element.startTimestamp && element.startTimestamp !== 0) || | ||
294 | (element.stopTimestamp && element.stopTimestamp !== this.video.duration) | ||
295 | ) { | ||
296 | return true | ||
297 | } | ||
298 | } | ||
299 | |||
300 | return false | ||
301 | } | ||
302 | |||
303 | private removeVideoFromPlaylist (playlist: PlaylistSummary, elementId: number) { | ||
304 | this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, elementId, this.video.id) | ||
203 | .subscribe( | 305 | .subscribe( |
204 | () => { | 306 | () => { |
205 | this.notifier.success($localize`Video removed from ${playlist.displayName}`) | 307 | this.notifier.success($localize`Video removed from ${playlist.displayName}`) |
@@ -213,7 +315,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
213 | ) | 315 | ) |
214 | } | 316 | } |
215 | 317 | ||
216 | private listenToPlaylistChanges () { | 318 | private listenToVideoPlaylistChange () { |
217 | this.unsubscribePlaylistChanges() | 319 | this.unsubscribePlaylistChanges() |
218 | 320 | ||
219 | this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id) | 321 | this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id) |
@@ -231,18 +333,30 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
231 | private rebuildPlaylists (existResult: VideoExistInPlaylist[]) { | 333 | private rebuildPlaylists (existResult: VideoExistInPlaylist[]) { |
232 | logger('Got existing results for %d.', this.video.id, existResult) | 334 | logger('Got existing results for %d.', this.video.id, existResult) |
233 | 335 | ||
336 | const oldPlaylists = this.videoPlaylists | ||
337 | |||
234 | this.videoPlaylists = [] | 338 | this.videoPlaylists = [] |
235 | for (const playlist of this.playlistsData) { | 339 | for (const playlist of this.playlistsData) { |
236 | const existingPlaylist = existResult.find(p => p.playlistId === playlist.id) | 340 | const existingPlaylists = existResult.filter(p => p.playlistId === playlist.id) |
237 | 341 | ||
238 | this.videoPlaylists.push({ | 342 | const playlistSummary = { |
239 | id: playlist.id, | 343 | id: playlist.id, |
344 | optionalRowDisplayed: false, | ||
240 | displayName: playlist.displayName, | 345 | displayName: playlist.displayName, |
241 | inPlaylist: !!existingPlaylist, | 346 | elements: existingPlaylists.map(e => ({ |
242 | playlistElementId: existingPlaylist ? existingPlaylist.playlistElementId : undefined, | 347 | enabled: true, |
243 | startTimestamp: existingPlaylist ? existingPlaylist.startTimestamp : undefined, | 348 | playlistElementId: e.playlistElementId, |
244 | stopTimestamp: existingPlaylist ? existingPlaylist.stopTimestamp : undefined | 349 | startTimestamp: e.startTimestamp || 0, |
245 | }) | 350 | stopTimestamp: e.stopTimestamp || this.video.duration |
351 | })) | ||
352 | } | ||
353 | |||
354 | const oldPlaylist = oldPlaylists.find(p => p.id === playlist.id) | ||
355 | playlistSummary.optionalRowDisplayed = oldPlaylist | ||
356 | ? oldPlaylist.optionalRowDisplayed | ||
357 | : this.isOptionalRowDisplayed(playlistSummary) | ||
358 | |||
359 | this.videoPlaylists.push(playlistSummary) | ||
246 | } | 360 | } |
247 | 361 | ||
248 | logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists) | 362 | logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists) |
@@ -250,20 +364,22 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
250 | this.cd.markForCheck() | 364 | this.cd.markForCheck() |
251 | } | 365 | } |
252 | 366 | ||
253 | private addVideoInPlaylist (playlist: PlaylistSummary) { | 367 | private addVideoInPlaylist (playlist: PlaylistSummary, element: PlaylistElement) { |
254 | const body: VideoPlaylistElementCreate = { videoId: this.video.id } | 368 | const body: VideoPlaylistElementCreate = { videoId: this.video.id } |
255 | 369 | ||
256 | if (this.timestampOptions.startTimestampEnabled) body.startTimestamp = this.timestampOptions.startTimestamp | 370 | if (element.startTimestamp) body.startTimestamp = element.startTimestamp |
257 | if (this.timestampOptions.stopTimestampEnabled) body.stopTimestamp = this.timestampOptions.stopTimestamp | 371 | if (element.stopTimestamp && element.stopTimestamp !== this.video.duration) body.stopTimestamp = element.stopTimestamp |
258 | 372 | ||
259 | this.videoPlaylistService.addVideoInPlaylist(playlist.id, body) | 373 | this.videoPlaylistService.addVideoInPlaylist(playlist.id, body) |
260 | .subscribe( | 374 | .subscribe( |
261 | () => { | 375 | res => { |
262 | const message = body.startTimestamp || body.stopTimestamp | 376 | const message = body.startTimestamp || body.stopTimestamp |
263 | ? $localize`Video added in ${playlist.displayName} at timestamps ${this.formatTimestamp(playlist)}` | 377 | ? $localize`Video added in ${playlist.displayName} at timestamps ${this.formatTimestamp(element)}` |
264 | : $localize`Video added in ${playlist.displayName}` | 378 | : $localize`Video added in ${playlist.displayName}` |
265 | 379 | ||
266 | this.notifier.success(message) | 380 | this.notifier.success(message) |
381 | |||
382 | if (element) element.playlistElementId = res.videoPlaylistElement.id | ||
267 | }, | 383 | }, |
268 | 384 | ||
269 | err => { | 385 | err => { |
@@ -273,4 +389,11 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
273 | () => this.cd.markForCheck() | 389 | () => this.cd.markForCheck() |
274 | ) | 390 | ) |
275 | } | 391 | } |
392 | |||
393 | private formatTimestamp (element: PlaylistElement) { | ||
394 | const start = element.startTimestamp ? secondsToTime(element.startTimestamp) : '' | ||
395 | const stop = element.stopTimestamp ? secondsToTime(element.stopTimestamp) : '' | ||
396 | |||
397 | return `(${start}-${stop})` | ||
398 | } | ||
276 | } | 399 | } |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss index afd775b25..082a5d9b2 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss | |||
@@ -22,12 +22,16 @@ my-video-thumbnail, | |||
22 | } | 22 | } |
23 | 23 | ||
24 | .video { | 24 | .video { |
25 | display: flex; | 25 | display: grid; |
26 | align-items: center; | 26 | grid-template-columns: 1fr auto; |
27 | background-color: pvar(--mainBackgroundColor); | 27 | background-color: pvar(--mainBackgroundColor); |
28 | padding: 10px; | 28 | padding: 10px; |
29 | border-bottom: 1px solid $separator-border-color; | 29 | border-bottom: 1px solid $separator-border-color; |
30 | 30 | ||
31 | .more { | ||
32 | display: flex; | ||
33 | } | ||
34 | |||
31 | &:hover { | 35 | &:hover { |
32 | background-color: rgba(0, 0, 0, 0.05); | 36 | background-color: rgba(0, 0, 0, 0.05); |
33 | 37 | ||
@@ -164,6 +168,7 @@ my-video-thumbnail, | |||
164 | my-edit-button { | 168 | my-edit-button { |
165 | display: inline-flex; | 169 | display: inline-flex; |
166 | height: max-content; | 170 | height: max-content; |
171 | margin: auto; | ||
167 | } | 172 | } |
168 | } | 173 | } |
169 | 174 | ||
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts index 5879c4978..7c083ae26 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts | |||
@@ -78,7 +78,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit { | |||
78 | if (!this.playlistElement || !this.playlistElement.video) return {} | 78 | if (!this.playlistElement || !this.playlistElement.video) return {} |
79 | 79 | ||
80 | return { | 80 | return { |
81 | videoId: this.playlistElement.video.uuid, | 81 | playlistPosition: this.playlistElement.position, |
82 | start: this.playlistElement.startTimestamp, | 82 | start: this.playlistElement.startTimestamp, |
83 | stop: this.playlistElement.stopTimestamp, | 83 | stop: this.playlistElement.stopTimestamp, |
84 | resume: true | 84 | resume: true |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts index cc3d04b9e..1b87e0b2a 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as debug from 'debug' | 1 | import * as debug from 'debug' |
2 | import { uniq } from 'lodash-es' | 2 | import { uniq } from 'lodash-es' |
3 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' | 3 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' |
4 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' | 4 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap, distinctUntilChanged } from 'rxjs/operators' |
5 | import { HttpClient, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { Injectable, NgZone } from '@angular/core' | 6 | import { Injectable, NgZone } from '@angular/core' |
7 | import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core' | 7 | import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core' |
@@ -53,6 +53,7 @@ export class VideoPlaylistService { | |||
53 | ) { | 53 | ) { |
54 | this.videoExistsInPlaylistObservable = merge( | 54 | this.videoExistsInPlaylistObservable = merge( |
55 | this.videoExistsInPlaylistNotifier.pipe( | 55 | this.videoExistsInPlaylistNotifier.pipe( |
56 | distinctUntilChanged(), | ||
56 | // We leave Angular zone so Protractor does not get stuck | 57 | // We leave Angular zone so Protractor does not get stuck |
57 | bufferTime(500, leaveZone(this.ngZone, asyncScheduler)), | 58 | bufferTime(500, leaveZone(this.ngZone, asyncScheduler)), |
58 | filter(videoIds => videoIds.length !== 0), | 59 | filter(videoIds => videoIds.length !== 0), |
@@ -215,10 +216,13 @@ export class VideoPlaylistService { | |||
215 | map(this.restExtractor.extractDataBool), | 216 | map(this.restExtractor.extractDataBool), |
216 | tap(() => { | 217 | tap(() => { |
217 | const existsResult = this.videoExistsCache[videoId] | 218 | const existsResult = this.videoExistsCache[videoId] |
218 | const elem = existsResult.find(e => e.playlistElementId === playlistElementId) | ||
219 | 219 | ||
220 | elem.startTimestamp = body.startTimestamp | 220 | if (existsResult) { |
221 | elem.stopTimestamp = body.stopTimestamp | 221 | const elem = existsResult.find(e => e.playlistElementId === playlistElementId) |
222 | |||
223 | elem.startTimestamp = body.startTimestamp | ||
224 | elem.stopTimestamp = body.stopTimestamp | ||
225 | } | ||
222 | 226 | ||
223 | this.runPlaylistCheck(videoId) | 227 | this.runPlaylistCheck(videoId) |
224 | }), | 228 | }), |
@@ -233,7 +237,11 @@ export class VideoPlaylistService { | |||
233 | tap(() => { | 237 | tap(() => { |
234 | if (!videoId) return | 238 | if (!videoId) return |
235 | 239 | ||
236 | this.videoExistsCache[videoId] = this.videoExistsCache[videoId].filter(e => e.playlistElementId !== playlistElementId) | 240 | if (this.videoExistsCache[videoId]) { |
241 | this.videoExistsCache[videoId] = this.videoExistsCache[videoId] | ||
242 | .filter(e => e.playlistElementId !== playlistElementId) | ||
243 | } | ||
244 | |||
237 | this.runPlaylistCheck(videoId) | 245 | this.runPlaylistCheck(videoId) |
238 | }), | 246 | }), |
239 | catchError(err => this.restExtractor.handleError(err)) | 247 | catchError(err => this.restExtractor.handleError(err)) |