diff options
author | Chocobozzz <me@florianbigard.com> | 2020-08-20 16:18:16 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-08-21 15:39:51 +0200 |
commit | 7294aab0c879ef96c0fde15c389a2c4c1463d3c7 (patch) | |
tree | bad1176720ee04266eba5470e9956e3a7871e349 /client | |
parent | f95628636b6ccdf3eae2449ca718e075b072f678 (diff) | |
download | PeerTube-7294aab0c879ef96c0fde15c389a2c4c1463d3c7.tar.gz PeerTube-7294aab0c879ef96c0fde15c389a2c4c1463d3c7.tar.zst PeerTube-7294aab0c879ef96c0fde15c389a2c4c1463d3c7.zip |
Add ability to set custom field to video form
Diffstat (limited to 'client')
21 files changed, 221 insertions, 66 deletions
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/+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 92d06aa12..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,8 +1,8 @@ | |||
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 { | 7 | import { |
8 | VIDEO_CATEGORY_VALIDATOR, | 8 | VIDEO_CATEGORY_VALIDATOR, |
@@ -21,6 +21,7 @@ import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem | |||
21 | import { InstanceService } from '@app/shared/shared-instance' | 21 | import { InstanceService } from '@app/shared/shared-instance' |
22 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' | 22 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' |
23 | 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' | ||
24 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' | 25 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' |
25 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 26 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
26 | 27 | ||
@@ -39,9 +40,12 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
39 | @Input() schedulePublicationPossible = true | 40 | @Input() schedulePublicationPossible = true |
40 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] | 41 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] |
41 | @Input() waitTranscodingEnabled = true | 42 | @Input() waitTranscodingEnabled = true |
43 | @Input() type: 'import-url' | 'import-torrent' | 'upload' | 'update' | ||
42 | 44 | ||
43 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent | 45 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent |
44 | 46 | ||
47 | @Output() pluginFieldsAdded = new EventEmitter<void>() | ||
48 | |||
45 | // So that it can be accessed in the template | 49 | // So that it can be accessed in the template |
46 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY | 50 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY |
47 | 51 | ||
@@ -53,6 +57,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
53 | tagValidators: ValidatorFn[] | 57 | tagValidators: ValidatorFn[] |
54 | tagValidatorsMessages: { [ name: string ]: string } | 58 | tagValidatorsMessages: { [ name: string ]: string } |
55 | 59 | ||
60 | pluginDataFormGroup: FormGroup | ||
61 | |||
56 | schedulePublicationEnabled = false | 62 | schedulePublicationEnabled = false |
57 | 63 | ||
58 | calendarLocale: any = {} | 64 | calendarLocale: any = {} |
@@ -64,6 +70,11 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
64 | 70 | ||
65 | serverConfig: ServerConfig | 71 | serverConfig: ServerConfig |
66 | 72 | ||
73 | pluginFields: { | ||
74 | commonOptions: RegisterClientFormFieldOptions | ||
75 | videoFormOptions: RegisterClientVideoFieldOptions | ||
76 | }[] = [] | ||
77 | |||
67 | private schedulerInterval: any | 78 | private schedulerInterval: any |
68 | private firstPatchDone = false | 79 | private firstPatchDone = false |
69 | private initialVideoCaptions: string[] = [] | 80 | private initialVideoCaptions: string[] = [] |
@@ -72,9 +83,11 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
72 | private formValidatorService: FormValidatorService, | 83 | private formValidatorService: FormValidatorService, |
73 | private videoService: VideoService, | 84 | private videoService: VideoService, |
74 | private serverService: ServerService, | 85 | private serverService: ServerService, |
86 | private pluginService: PluginService, | ||
75 | private instanceService: InstanceService, | 87 | private instanceService: InstanceService, |
76 | private i18nPrimengCalendarService: I18nPrimengCalendarService, | 88 | private i18nPrimengCalendarService: I18nPrimengCalendarService, |
77 | private ngZone: NgZone | 89 | private ngZone: NgZone, |
90 | private hooks: HooksService | ||
78 | ) { | 91 | ) { |
79 | this.calendarLocale = this.i18nPrimengCalendarService.getCalendarLocale() | 92 | this.calendarLocale = this.i18nPrimengCalendarService.getCalendarLocale() |
80 | this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone() | 93 | this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone() |
@@ -136,19 +149,26 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
136 | ngOnInit () { | 149 | ngOnInit () { |
137 | this.updateForm() | 150 | this.updateForm() |
138 | 151 | ||
152 | this.pluginService.ensurePluginsAreLoaded('video-edit') | ||
153 | .then(() => this.updatePluginFields()) | ||
154 | |||
139 | this.serverService.getVideoCategories() | 155 | this.serverService.getVideoCategories() |
140 | .subscribe(res => this.videoCategories = res) | 156 | .subscribe(res => this.videoCategories = res) |
157 | |||
141 | this.serverService.getVideoLicences() | 158 | this.serverService.getVideoLicences() |
142 | .subscribe(res => this.videoLicences = res) | 159 | .subscribe(res => this.videoLicences = res) |
160 | |||
143 | forkJoin([ | 161 | forkJoin([ |
144 | this.instanceService.getAbout(), | 162 | this.instanceService.getAbout(), |
145 | this.serverService.getVideoLanguages() | 163 | this.serverService.getVideoLanguages() |
146 | ]).pipe(map(([ about, languages ]) => ({ about, languages }))) | 164 | ]).pipe(map(([ about, languages ]) => ({ about, languages }))) |
147 | .subscribe(res => { | 165 | .subscribe(res => { |
148 | this.videoLanguages = res.languages | 166 | this.videoLanguages = res.languages |
149 | .map(l => res.about.instance.languages.includes(l.id) | 167 | .map(l => { |
150 | ? { ...l, group: $localize`Instance languages`, groupOrder: 0 } | 168 | return res.about.instance.languages.includes(l.id) |
151 | : { ...l, group: $localize`All languages`, groupOrder: 1 }) | 169 | ? { ...l, group: $localize`Instance languages`, groupOrder: 0 } |
170 | : { ...l, group: $localize`All languages`, groupOrder: 1 } | ||
171 | }) | ||
152 | .sort((a, b) => a.groupOrder - b.groupOrder) | 172 | .sort((a, b) => a.groupOrder - b.groupOrder) |
153 | }) | 173 | }) |
154 | 174 | ||
@@ -173,6 +193,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
173 | this.ngZone.runOutsideAngular(() => { | 193 | this.ngZone.runOutsideAngular(() => { |
174 | 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 |
175 | }) | 195 | }) |
196 | |||
197 | this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type }) | ||
176 | } | 198 | } |
177 | 199 | ||
178 | ngOnDestroy () { | 200 | ngOnDestroy () { |
@@ -223,6 +245,23 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
223 | }) | 245 | }) |
224 | } | 246 | } |
225 | 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 | |||
226 | private trackPrivacyChange () { | 265 | private trackPrivacyChange () { |
227 | // 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 |
228 | 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 825cb6df4..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 | |||
@@ -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 2107dc9d0..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 | |||
@@ -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/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index 871613b89..4e44a1865 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -9,7 +9,7 @@ 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, 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 { Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins' | 12 | import { FormFields, Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins' |
13 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' | 13 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' |
14 | import { | 14 | import { |
15 | ClientHook, | 15 | ClientHook, |
@@ -36,6 +36,7 @@ export class PluginService implements ClientHook { | |||
36 | 'video-watch': new ReplaySubject<boolean>(1), | 36 | 'video-watch': new ReplaySubject<boolean>(1), |
37 | signup: new ReplaySubject<boolean>(1), | 37 | signup: new ReplaySubject<boolean>(1), |
38 | login: new ReplaySubject<boolean>(1), | 38 | login: new ReplaySubject<boolean>(1), |
39 | 'video-edit': new ReplaySubject<boolean>(1), | ||
39 | embed: new ReplaySubject<boolean>(1) | 40 | embed: new ReplaySubject<boolean>(1) |
40 | } | 41 | } |
41 | 42 | ||
@@ -50,6 +51,9 @@ export class PluginService implements ClientHook { | |||
50 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} | 51 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} |
51 | 52 | ||
52 | private hooks: Hooks = {} | 53 | private hooks: Hooks = {} |
54 | private formFields: FormFields = { | ||
55 | video: [] | ||
56 | } | ||
53 | 57 | ||
54 | constructor ( | 58 | constructor ( |
55 | private authService: AuthService, | 59 | private authService: AuthService, |
@@ -188,9 +192,18 @@ export class PluginService implements ClientHook { | |||
188 | : PluginType.THEME | 192 | : PluginType.THEME |
189 | } | 193 | } |
190 | 194 | ||
195 | getRegisteredVideoFormFields (type: 'import-url' | 'import-torrent' | 'upload' | 'update') { | ||
196 | return this.formFields.video.filter(f => f.videoFormOptions.type === type) | ||
197 | } | ||
198 | |||
191 | private loadPlugin (pluginInfo: PluginInfo) { | 199 | private loadPlugin (pluginInfo: PluginInfo) { |
192 | return this.zone.runOutsideAngular(() => { | 200 | return this.zone.runOutsideAngular(() => { |
193 | return loadPlugin(this.hooks, pluginInfo, pluginInfo => this.buildPeerTubeHelpers(pluginInfo)) | 201 | return loadPlugin({ |
202 | hooks: this.hooks, | ||
203 | formFields: this.formFields, | ||
204 | pluginInfo, | ||
205 | peertubeHelpersFactory: pluginInfo => this.buildPeerTubeHelpers(pluginInfo) | ||
206 | }) | ||
194 | }) | 207 | }) |
195 | } | 208 | } |
196 | 209 | ||
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..c111ea7df --- /dev/null +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.html | |||
@@ -0,0 +1,35 @@ | |||
1 | <div [formGroup]="form"> | ||
2 | <label *ngIf="setting.type !== 'input-checkbox'" [attr.for]="setting.name" [innerHTML]="setting.label"></label> | ||
3 | |||
4 | <input *ngIf="setting.type === 'input'" type="text" [id]="setting.name" [formControlName]="setting.name" /> | ||
5 | |||
6 | <textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea> | ||
7 | |||
8 | <my-help *ngIf="setting.type === 'markdown-text'" helpType="markdownText"></my-help> | ||
9 | |||
10 | <my-help *ngIf="setting.type === 'markdown-enhanced'" helpType="markdownEnhanced"></my-help> | ||
11 | |||
12 | <my-markdown-textarea | ||
13 | *ngIf="setting.type === 'markdown-text'" | ||
14 | markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | ||
15 | [classes]="{ 'input-error': formErrors['settings.name'] }" | ||
16 | ></my-markdown-textarea> | ||
17 | |||
18 | <my-markdown-textarea | ||
19 | *ngIf="setting.type === 'markdown-enhanced'" | ||
20 | markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | ||
21 | [classes]="{ 'input-error': formErrors['settings.name'] }" | ||
22 | ></my-markdown-textarea> | ||
23 | |||
24 | <my-peertube-checkbox | ||
25 | *ngIf="setting.type === 'input-checkbox'" | ||
26 | [id]="setting.name" | ||
27 | [formControlName]="setting.name" | ||
28 | [labelInnerHTML]="setting.label" | ||
29 | ></my-peertube-checkbox> | ||
30 | |||
31 | <div *ngIf="formErrors[setting.name]" class="form-error"> | ||
32 | {{ formErrors[setting.name] }} | ||
33 | </div> | ||
34 | |||
35 | </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..70b3cf6c3 --- /dev/null +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.scss | |||
@@ -0,0 +1,18 @@ | |||
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 | } | ||
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/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts index 1946ac21f..a28988f87 100644 --- a/client/src/app/shared/shared-forms/shared-form.module.ts +++ b/client/src/app/shared/shared-forms/shared-form.module.ts | |||
@@ -15,6 +15,7 @@ import { ReactiveFileComponent } from './reactive-file.component' | |||
15 | import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select' | 15 | import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select' |
16 | import { TextareaAutoResizeDirective } from './textarea-autoresize.directive' | 16 | import { TextareaAutoResizeDirective } from './textarea-autoresize.directive' |
17 | import { TimestampInputComponent } from './timestamp-input.component' | 17 | import { TimestampInputComponent } from './timestamp-input.component' |
18 | import { DynamicFormFieldComponent } from './dynamic-form-field.component' | ||
18 | 19 | ||
19 | @NgModule({ | 20 | @NgModule({ |
20 | imports: [ | 21 | imports: [ |
@@ -41,7 +42,9 @@ import { TimestampInputComponent } from './timestamp-input.component' | |||
41 | SelectChannelComponent, | 42 | SelectChannelComponent, |
42 | SelectOptionsComponent, | 43 | SelectOptionsComponent, |
43 | SelectTagsComponent, | 44 | SelectTagsComponent, |
44 | SelectCheckboxComponent | 45 | SelectCheckboxComponent, |
46 | |||
47 | DynamicFormFieldComponent | ||
45 | ], | 48 | ], |
46 | 49 | ||
47 | exports: [ | 50 | exports: [ |
@@ -63,7 +66,9 @@ import { TimestampInputComponent } from './timestamp-input.component' | |||
63 | SelectChannelComponent, | 66 | SelectChannelComponent, |
64 | SelectOptionsComponent, | 67 | SelectOptionsComponent, |
65 | SelectTagsComponent, | 68 | SelectTagsComponent, |
66 | SelectCheckboxComponent | 69 | SelectCheckboxComponent, |
70 | |||
71 | DynamicFormFieldComponent | ||
67 | ], | 72 | ], |
68 | 73 | ||
69 | providers: [ | 74 | providers: [ |
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/root-helpers/plugins.ts b/client/src/root-helpers/plugins.ts index 011721761..4bc2c8eb2 100644 --- a/client/src/root-helpers/plugins.ts +++ b/client/src/root-helpers/plugins.ts | |||
@@ -1,6 +1,14 @@ | |||
1 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
2 | import { ClientHookName, ClientScript, RegisterClientHookOptions, ServerConfigPlugin, PluginType, clientHookObject } from '../../../shared/models' | ||
3 | import { RegisterClientHelpers } from 'src/types/register-client-option.model' | 1 | import { RegisterClientHelpers } from 'src/types/register-client-option.model' |
2 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
3 | import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' | ||
4 | import { | ||
5 | ClientHookName, | ||
6 | clientHookObject, | ||
7 | ClientScript, | ||
8 | PluginType, | ||
9 | RegisterClientHookOptions, | ||
10 | ServerConfigPlugin | ||
11 | } from '../../../shared/models' | ||
4 | import { ClientScript as ClientScriptModule } from '../types/client-script.model' | 12 | import { ClientScript as ClientScriptModule } from '../types/client-script.model' |
5 | import { importModule } from './utils' | 13 | import { importModule } from './utils' |
6 | 14 | ||
@@ -18,6 +26,13 @@ type PluginInfo = { | |||
18 | isTheme: boolean | 26 | isTheme: boolean |
19 | } | 27 | } |
20 | 28 | ||
29 | type FormFields = { | ||
30 | video: { | ||
31 | commonOptions: RegisterClientFormFieldOptions | ||
32 | videoFormOptions: RegisterClientVideoFieldOptions | ||
33 | }[] | ||
34 | } | ||
35 | |||
21 | async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, params?: any) { | 36 | async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, params?: any) { |
22 | if (!hooks[hookName]) return result | 37 | if (!hooks[hookName]) return result |
23 | 38 | ||
@@ -34,7 +49,13 @@ async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, p | |||
34 | return result | 49 | return result |
35 | } | 50 | } |
36 | 51 | ||
37 | function loadPlugin (hooks: Hooks, pluginInfo: PluginInfo, peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers) { | 52 | function loadPlugin (options: { |
53 | hooks: Hooks | ||
54 | pluginInfo: PluginInfo | ||
55 | peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers | ||
56 | formFields?: FormFields | ||
57 | }) { | ||
58 | const { hooks, pluginInfo, peertubeHelpersFactory, formFields } = options | ||
38 | const { plugin, clientScript } = pluginInfo | 59 | const { plugin, clientScript } = pluginInfo |
39 | 60 | ||
40 | const registerHook = (options: RegisterClientHookOptions) => { | 61 | const registerHook = (options: RegisterClientHookOptions) => { |
@@ -54,12 +75,23 @@ function loadPlugin (hooks: Hooks, pluginInfo: PluginInfo, peertubeHelpersFactor | |||
54 | }) | 75 | }) |
55 | } | 76 | } |
56 | 77 | ||
78 | const registerVideoField = (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => { | ||
79 | if (!formFields) { | ||
80 | throw new Error('Video field registration is not supported') | ||
81 | } | ||
82 | |||
83 | formFields.video.push({ | ||
84 | commonOptions, | ||
85 | videoFormOptions | ||
86 | }) | ||
87 | } | ||
88 | |||
57 | const peertubeHelpers = peertubeHelpersFactory(pluginInfo) | 89 | const peertubeHelpers = peertubeHelpersFactory(pluginInfo) |
58 | 90 | ||
59 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | 91 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) |
60 | 92 | ||
61 | return importModule(clientScript.script) | 93 | return importModule(clientScript.script) |
62 | .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) | 94 | .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, peertubeHelpers })) |
63 | .then(() => sortHooksByPriority(hooks)) | 95 | .then(() => sortHooksByPriority(hooks)) |
64 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | 96 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) |
65 | } | 97 | } |
@@ -68,6 +100,7 @@ export { | |||
68 | HookStructValue, | 100 | HookStructValue, |
69 | Hooks, | 101 | Hooks, |
70 | PluginInfo, | 102 | PluginInfo, |
103 | FormFields, | ||
71 | loadPlugin, | 104 | loadPlugin, |
72 | runHook | 105 | runHook |
73 | } | 106 | } |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index fe65794f7..c79471005 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -750,7 +750,11 @@ export class PeerTubeEmbed { | |||
750 | isTheme: false | 750 | isTheme: false |
751 | } | 751 | } |
752 | 752 | ||
753 | await loadPlugin(this.peertubeHooks, pluginInfo, _ => this.buildPeerTubeHelpers(translations)) | 753 | await loadPlugin({ |
754 | hooks: this.peertubeHooks, | ||
755 | pluginInfo, | ||
756 | peertubeHelpersFactory: _ => this.buildPeerTubeHelpers(translations) | ||
757 | }) | ||
754 | } | 758 | } |
755 | } | 759 | } |
756 | } | 760 | } |
diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts index dff00e9dd..e3c6d803d 100644 --- a/client/src/types/register-client-option.model.ts +++ b/client/src/types/register-client-option.model.ts | |||
@@ -1,8 +1,11 @@ | |||
1 | import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' | ||
1 | import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model' | 2 | import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model' |
2 | 3 | ||
3 | export type RegisterClientOptions = { | 4 | export type RegisterClientOptions = { |
4 | registerHook: (options: RegisterClientHookOptions) => void | 5 | registerHook: (options: RegisterClientHookOptions) => void |
5 | 6 | ||
7 | registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void | ||
8 | |||
6 | peertubeHelpers: RegisterClientHelpers | 9 | peertubeHelpers: RegisterClientHelpers |
7 | } | 10 | } |
8 | 11 | ||