mirror of https://github.com/Chocobozzz/PeerTube
Add ability to set custom field to video form
parent
f95628636b
commit
7294aab0c8
|
@ -7,38 +7,7 @@
|
|||
|
||||
<form *ngIf="hasRegisteredSettings()" role="form" (ngSubmit)="formValidated()" [formGroup]="form">
|
||||
<div class="form-group" *ngFor="let setting of registeredSettings">
|
||||
<label *ngIf="setting.type !== 'input-checkbox'" [attr.for]="setting.name" [innerHTML]="setting.label"></label>
|
||||
|
||||
<input *ngIf="setting.type === 'input'" type="text" [id]="setting.name" [formControlName]="setting.name" />
|
||||
|
||||
<textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea>
|
||||
|
||||
<my-help *ngIf="setting.type === 'markdown-text'" helpType="markdownText"></my-help>
|
||||
|
||||
<my-help *ngIf="setting.type === 'markdown-enhanced'" helpType="markdownEnhanced"></my-help>
|
||||
|
||||
<my-markdown-textarea
|
||||
*ngIf="setting.type === 'markdown-text'"
|
||||
markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px"
|
||||
[classes]="{ 'input-error': formErrors['settings.name'] }"
|
||||
></my-markdown-textarea>
|
||||
|
||||
<my-markdown-textarea
|
||||
*ngIf="setting.type === 'markdown-enhanced'"
|
||||
markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px"
|
||||
[classes]="{ 'input-error': formErrors['settings.name'] }"
|
||||
></my-markdown-textarea>
|
||||
|
||||
<my-peertube-checkbox
|
||||
*ngIf="setting.type === 'input-checkbox'"
|
||||
[id]="setting.name"
|
||||
[formControlName]="setting.name"
|
||||
[labelInnerHTML]="setting.label"
|
||||
></my-peertube-checkbox>
|
||||
|
||||
<div *ngIf="formErrors[setting.name]" class="form-error">
|
||||
{{ formErrors[setting.name] }}
|
||||
</div>
|
||||
<my-dynamic-form-field [form]="form" [setting]="setting" [formErrors]="formErrors"></my-dynamic-form-field>
|
||||
</div>
|
||||
|
||||
<input type="submit" i18n value="Update plugin settings" [disabled]="!form.valid">
|
||||
|
|
|
@ -5,22 +5,6 @@ h2 {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input:not([type=submit]) {
|
||||
@include peertube-input-text(340px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(340px, 200px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.peertube-select-container {
|
||||
@include peertube-select-container(340px);
|
||||
}
|
||||
|
||||
input[type=submit], button {
|
||||
@include peertube-button;
|
||||
@include orange-button;
|
||||
|
|
|
@ -265,6 +265,21 @@
|
|||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container ngbNavItem *ngIf="pluginFields.length !== 0">
|
||||
<a ngbNavLink i18n>Plugin settings</a>
|
||||
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row plugin-settings">
|
||||
|
||||
<div class="col-md-12 col-xl-8">
|
||||
<div *ngFor="let pluginSetting of pluginFields" class="form-group">
|
||||
<my-dynamic-form-field [form]="pluginDataFormGroup" [formErrors]="formErrors" [setting]="pluginSetting.commonOptions"></my-dynamic-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
label {
|
||||
label,
|
||||
my-dynamic-form-field ::ng-deep label {
|
||||
font-weight: $font-regular;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { forkJoin } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { Component, Input, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||
import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'
|
||||
import { ServerService } from '@app/core'
|
||||
import { HooksService, PluginService, ServerService } from '@app/core'
|
||||
import { removeElementFromArray } from '@app/helpers'
|
||||
import {
|
||||
VIDEO_CATEGORY_VALIDATOR,
|
||||
|
@ -21,6 +21,7 @@ import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem
|
|||
import { InstanceService } from '@app/shared/shared-instance'
|
||||
import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main'
|
||||
import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models'
|
||||
import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model'
|
||||
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
|
||||
import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
|
||||
|
||||
|
@ -39,9 +40,12 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
@Input() schedulePublicationPossible = true
|
||||
@Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = []
|
||||
@Input() waitTranscodingEnabled = true
|
||||
@Input() type: 'import-url' | 'import-torrent' | 'upload' | 'update'
|
||||
|
||||
@ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent
|
||||
|
||||
@Output() pluginFieldsAdded = new EventEmitter<void>()
|
||||
|
||||
// So that it can be accessed in the template
|
||||
readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
|
||||
|
||||
|
@ -53,6 +57,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
tagValidators: ValidatorFn[]
|
||||
tagValidatorsMessages: { [ name: string ]: string }
|
||||
|
||||
pluginDataFormGroup: FormGroup
|
||||
|
||||
schedulePublicationEnabled = false
|
||||
|
||||
calendarLocale: any = {}
|
||||
|
@ -64,6 +70,11 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
|
||||
serverConfig: ServerConfig
|
||||
|
||||
pluginFields: {
|
||||
commonOptions: RegisterClientFormFieldOptions
|
||||
videoFormOptions: RegisterClientVideoFieldOptions
|
||||
}[] = []
|
||||
|
||||
private schedulerInterval: any
|
||||
private firstPatchDone = false
|
||||
private initialVideoCaptions: string[] = []
|
||||
|
@ -72,9 +83,11 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
private formValidatorService: FormValidatorService,
|
||||
private videoService: VideoService,
|
||||
private serverService: ServerService,
|
||||
private pluginService: PluginService,
|
||||
private instanceService: InstanceService,
|
||||
private i18nPrimengCalendarService: I18nPrimengCalendarService,
|
||||
private ngZone: NgZone
|
||||
private ngZone: NgZone,
|
||||
private hooks: HooksService
|
||||
) {
|
||||
this.calendarLocale = this.i18nPrimengCalendarService.getCalendarLocale()
|
||||
this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone()
|
||||
|
@ -136,19 +149,26 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
ngOnInit () {
|
||||
this.updateForm()
|
||||
|
||||
this.pluginService.ensurePluginsAreLoaded('video-edit')
|
||||
.then(() => this.updatePluginFields())
|
||||
|
||||
this.serverService.getVideoCategories()
|
||||
.subscribe(res => this.videoCategories = res)
|
||||
|
||||
this.serverService.getVideoLicences()
|
||||
.subscribe(res => this.videoLicences = res)
|
||||
|
||||
forkJoin([
|
||||
this.instanceService.getAbout(),
|
||||
this.serverService.getVideoLanguages()
|
||||
]).pipe(map(([ about, languages ]) => ({ about, languages })))
|
||||
.subscribe(res => {
|
||||
this.videoLanguages = res.languages
|
||||
.map(l => res.about.instance.languages.includes(l.id)
|
||||
? { ...l, group: $localize`Instance languages`, groupOrder: 0 }
|
||||
: { ...l, group: $localize`All languages`, groupOrder: 1 })
|
||||
.map(l => {
|
||||
return res.about.instance.languages.includes(l.id)
|
||||
? { ...l, group: $localize`Instance languages`, groupOrder: 0 }
|
||||
: { ...l, group: $localize`All languages`, groupOrder: 1 }
|
||||
})
|
||||
.sort((a, b) => a.groupOrder - b.groupOrder)
|
||||
})
|
||||
|
||||
|
@ -173,6 +193,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
this.ngZone.runOutsideAngular(() => {
|
||||
this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute
|
||||
})
|
||||
|
||||
this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type })
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
|
@ -223,6 +245,23 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
})
|
||||
}
|
||||
|
||||
private updatePluginFields () {
|
||||
this.pluginFields = this.pluginService.getRegisteredVideoFormFields(this.type)
|
||||
|
||||
if (this.pluginFields.length === 0) return
|
||||
|
||||
const obj: any = {}
|
||||
|
||||
for (const setting of this.pluginFields) {
|
||||
obj[setting.commonOptions.name] = new FormControl(setting.commonOptions.default)
|
||||
}
|
||||
|
||||
this.pluginDataFormGroup = new FormGroup(obj)
|
||||
this.form.addControl('pluginData', this.pluginDataFormGroup)
|
||||
|
||||
this.pluginFieldsAdded.emit()
|
||||
}
|
||||
|
||||
private trackPrivacyChange () {
|
||||
// We will update the schedule input and the wait transcoding checkbox validators
|
||||
this.form.controls[ 'privacy' ]
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<my-video-edit
|
||||
[form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false"
|
||||
[validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
|
||||
type="import-torrent"
|
||||
></my-video-edit>
|
||||
|
||||
<div class="submit-container">
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<my-video-edit
|
||||
[form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false"
|
||||
[validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
|
||||
type="import-url"
|
||||
></my-video-edit>
|
||||
|
||||
<div class="submit-container">
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
[form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions"
|
||||
[validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
|
||||
[waitTranscodingEnabled]="waitTranscodingEnabled"
|
||||
type="upload"
|
||||
></my-video-edit>
|
||||
|
||||
<div class="submit-container">
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
[form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible"
|
||||
[validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
|
||||
[videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled"
|
||||
type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()"
|
||||
></my-video-edit>
|
||||
|
||||
<div class="submit-container">
|
||||
<my-button className="orange-button" i18n-label label="Update" icon="circle-tick"
|
||||
(click)="update()" (keydown.enter)="update()"
|
||||
(click)="update()" (keydown.enter)="update()"
|
||||
[disabled]="!form.valid || isUpdatingVideo === true"
|
||||
></my-button>
|
||||
</div>
|
||||
|
|
|
@ -126,6 +126,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
)
|
||||
}
|
||||
|
||||
hydratePluginFieldsFromVideo () {
|
||||
if (!this.video.pluginData) return
|
||||
|
||||
this.form.patchValue({
|
||||
pluginData: this.video.pluginData
|
||||
})
|
||||
}
|
||||
|
||||
private hydrateFormFromVideo () {
|
||||
this.form.patchValue(this.video.toFormPatch())
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { RestExtractor } from '@app/core/rest'
|
|||
import { ServerService } from '@app/core/server/server.service'
|
||||
import { getDevLocale, isOnDevLocale } from '@app/helpers'
|
||||
import { CustomModalComponent } from '@app/modal/custom-modal.component'
|
||||
import { Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins'
|
||||
import { FormFields, Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins'
|
||||
import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
|
||||
import {
|
||||
ClientHook,
|
||||
|
@ -36,6 +36,7 @@ export class PluginService implements ClientHook {
|
|||
'video-watch': new ReplaySubject<boolean>(1),
|
||||
signup: new ReplaySubject<boolean>(1),
|
||||
login: new ReplaySubject<boolean>(1),
|
||||
'video-edit': new ReplaySubject<boolean>(1),
|
||||
embed: new ReplaySubject<boolean>(1)
|
||||
}
|
||||
|
||||
|
@ -50,6 +51,9 @@ export class PluginService implements ClientHook {
|
|||
private loadingScopes: { [id in PluginClientScope]?: boolean } = {}
|
||||
|
||||
private hooks: Hooks = {}
|
||||
private formFields: FormFields = {
|
||||
video: []
|
||||
}
|
||||
|
||||
constructor (
|
||||
private authService: AuthService,
|
||||
|
@ -188,9 +192,18 @@ export class PluginService implements ClientHook {
|
|||
: PluginType.THEME
|
||||
}
|
||||
|
||||
getRegisteredVideoFormFields (type: 'import-url' | 'import-torrent' | 'upload' | 'update') {
|
||||
return this.formFields.video.filter(f => f.videoFormOptions.type === type)
|
||||
}
|
||||
|
||||
private loadPlugin (pluginInfo: PluginInfo) {
|
||||
return this.zone.runOutsideAngular(() => {
|
||||
return loadPlugin(this.hooks, pluginInfo, pluginInfo => this.buildPeerTubeHelpers(pluginInfo))
|
||||
return loadPlugin({
|
||||
hooks: this.hooks,
|
||||
formFields: this.formFields,
|
||||
pluginInfo,
|
||||
peertubeHelpersFactory: pluginInfo => this.buildPeerTubeHelpers(pluginInfo)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<div [formGroup]="form">
|
||||
<label *ngIf="setting.type !== 'input-checkbox'" [attr.for]="setting.name" [innerHTML]="setting.label"></label>
|
||||
|
||||
<input *ngIf="setting.type === 'input'" type="text" [id]="setting.name" [formControlName]="setting.name" />
|
||||
|
||||
<textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea>
|
||||
|
||||
<my-help *ngIf="setting.type === 'markdown-text'" helpType="markdownText"></my-help>
|
||||
|
||||
<my-help *ngIf="setting.type === 'markdown-enhanced'" helpType="markdownEnhanced"></my-help>
|
||||
|
||||
<my-markdown-textarea
|
||||
*ngIf="setting.type === 'markdown-text'"
|
||||
markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px"
|
||||
[classes]="{ 'input-error': formErrors['settings.name'] }"
|
||||
></my-markdown-textarea>
|
||||
|
||||
<my-markdown-textarea
|
||||
*ngIf="setting.type === 'markdown-enhanced'"
|
||||
markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px"
|
||||
[classes]="{ 'input-error': formErrors['settings.name'] }"
|
||||
></my-markdown-textarea>
|
||||
|
||||
<my-peertube-checkbox
|
||||
*ngIf="setting.type === 'input-checkbox'"
|
||||
[id]="setting.name"
|
||||
[formControlName]="setting.name"
|
||||
[labelInnerHTML]="setting.label"
|
||||
></my-peertube-checkbox>
|
||||
|
||||
<div *ngIf="formErrors[setting.name]" class="form-error">
|
||||
{{ formErrors[setting.name] }}
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,18 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
input:not([type=submit]) {
|
||||
@include peertube-input-text(340px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(340px, 200px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.peertube-select-container {
|
||||
@include peertube-select-container(340px);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Component, Input } from '@angular/core'
|
||||
import { FormGroup } from '@angular/forms'
|
||||
import { RegisterClientFormFieldOptions } from '@shared/models'
|
||||
|
||||
@Component({
|
||||
selector: 'my-dynamic-form-field',
|
||||
templateUrl: './dynamic-form-field.component.html',
|
||||
styleUrls: [ './dynamic-form-field.component.scss' ]
|
||||
})
|
||||
|
||||
export class DynamicFormFieldComponent {
|
||||
@Input() form: FormGroup
|
||||
@Input() formErrors: any
|
||||
@Input() setting: RegisterClientFormFieldOptions
|
||||
}
|
|
@ -15,6 +15,7 @@ import { ReactiveFileComponent } from './reactive-file.component'
|
|||
import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select'
|
||||
import { TextareaAutoResizeDirective } from './textarea-autoresize.directive'
|
||||
import { TimestampInputComponent } from './timestamp-input.component'
|
||||
import { DynamicFormFieldComponent } from './dynamic-form-field.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -41,7 +42,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
|
|||
SelectChannelComponent,
|
||||
SelectOptionsComponent,
|
||||
SelectTagsComponent,
|
||||
SelectCheckboxComponent
|
||||
SelectCheckboxComponent,
|
||||
|
||||
DynamicFormFieldComponent
|
||||
],
|
||||
|
||||
exports: [
|
||||
|
@ -63,7 +66,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
|
|||
SelectChannelComponent,
|
||||
SelectOptionsComponent,
|
||||
SelectTagsComponent,
|
||||
SelectCheckboxComponent
|
||||
SelectCheckboxComponent,
|
||||
|
||||
DynamicFormFieldComponent
|
||||
],
|
||||
|
||||
providers: [
|
||||
|
|
|
@ -25,6 +25,8 @@ export class VideoEdit implements VideoUpdate {
|
|||
scheduleUpdate?: VideoScheduleUpdate
|
||||
originallyPublishedAt?: Date | string
|
||||
|
||||
pluginData?: any
|
||||
|
||||
constructor (
|
||||
video?: Video & {
|
||||
tags: string[],
|
||||
|
@ -55,10 +57,12 @@ export class VideoEdit implements VideoUpdate {
|
|||
|
||||
this.scheduleUpdate = video.scheduledUpdate
|
||||
this.originallyPublishedAt = video.originallyPublishedAt ? new Date(video.originallyPublishedAt) : null
|
||||
|
||||
this.pluginData = video.pluginData
|
||||
}
|
||||
}
|
||||
|
||||
patch (values: { [ id: string ]: string }) {
|
||||
patch (values: { [ id: string ]: any }) {
|
||||
Object.keys(values).forEach((key) => {
|
||||
this[ key ] = values[ key ]
|
||||
})
|
||||
|
|
|
@ -84,6 +84,8 @@ export class Video implements VideoServerModel {
|
|||
currentTime: number
|
||||
}
|
||||
|
||||
pluginData?: any
|
||||
|
||||
static buildClientUrl (videoUUID: string) {
|
||||
return '/videos/watch/' + videoUUID
|
||||
}
|
||||
|
@ -152,6 +154,8 @@ export class Video implements VideoServerModel {
|
|||
|
||||
this.originInstanceHost = this.account.host
|
||||
this.originInstanceUrl = 'https://' + this.originInstanceHost
|
||||
|
||||
this.pluginData = hash.pluginData
|
||||
}
|
||||
|
||||
isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
|
||||
|
|
|
@ -96,6 +96,7 @@ export class VideoService implements VideosProvider {
|
|||
downloadEnabled: video.downloadEnabled,
|
||||
thumbnailfile: video.thumbnailfile,
|
||||
previewfile: video.previewfile,
|
||||
pluginData: video.pluginData,
|
||||
scheduleUpdate,
|
||||
originallyPublishedAt
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
|
||||
import { ClientHookName, ClientScript, RegisterClientHookOptions, ServerConfigPlugin, PluginType, clientHookObject } from '../../../shared/models'
|
||||
import { RegisterClientHelpers } from 'src/types/register-client-option.model'
|
||||
import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
|
||||
import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model'
|
||||
import {
|
||||
ClientHookName,
|
||||
clientHookObject,
|
||||
ClientScript,
|
||||
PluginType,
|
||||
RegisterClientHookOptions,
|
||||
ServerConfigPlugin
|
||||
} from '../../../shared/models'
|
||||
import { ClientScript as ClientScriptModule } from '../types/client-script.model'
|
||||
import { importModule } from './utils'
|
||||
|
||||
|
@ -18,6 +26,13 @@ type PluginInfo = {
|
|||
isTheme: boolean
|
||||
}
|
||||
|
||||
type FormFields = {
|
||||
video: {
|
||||
commonOptions: RegisterClientFormFieldOptions
|
||||
videoFormOptions: RegisterClientVideoFieldOptions
|
||||
}[]
|
||||
}
|
||||
|
||||
async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, params?: any) {
|
||||
if (!hooks[hookName]) return result
|
||||
|
||||
|
@ -34,7 +49,13 @@ async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, p
|
|||
return result
|
||||
}
|
||||
|
||||
function loadPlugin (hooks: Hooks, pluginInfo: PluginInfo, peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers) {
|
||||
function loadPlugin (options: {
|
||||
hooks: Hooks
|
||||
pluginInfo: PluginInfo
|
||||
peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers
|
||||
formFields?: FormFields
|
||||
}) {
|
||||
const { hooks, pluginInfo, peertubeHelpersFactory, formFields } = options
|
||||
const { plugin, clientScript } = pluginInfo
|
||||
|
||||
const registerHook = (options: RegisterClientHookOptions) => {
|
||||
|
@ -54,12 +75,23 @@ function loadPlugin (hooks: Hooks, pluginInfo: PluginInfo, peertubeHelpersFactor
|
|||
})
|
||||
}
|
||||
|
||||
const registerVideoField = (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => {
|
||||
if (!formFields) {
|
||||
throw new Error('Video field registration is not supported')
|
||||
}
|
||||
|
||||
formFields.video.push({
|
||||
commonOptions,
|
||||
videoFormOptions
|
||||
})
|
||||
}
|
||||
|
||||
const peertubeHelpers = peertubeHelpersFactory(pluginInfo)
|
||||
|
||||
console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
|
||||
|
||||
return importModule(clientScript.script)
|
||||
.then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers }))
|
||||
.then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, peertubeHelpers }))
|
||||
.then(() => sortHooksByPriority(hooks))
|
||||
.catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
|
||||
}
|
||||
|
@ -68,6 +100,7 @@ export {
|
|||
HookStructValue,
|
||||
Hooks,
|
||||
PluginInfo,
|
||||
FormFields,
|
||||
loadPlugin,
|
||||
runHook
|
||||
}
|
||||
|
|
|
@ -750,7 +750,11 @@ export class PeerTubeEmbed {
|
|||
isTheme: false
|
||||
}
|
||||
|
||||
await loadPlugin(this.peertubeHooks, pluginInfo, _ => this.buildPeerTubeHelpers(translations))
|
||||
await loadPlugin({
|
||||
hooks: this.peertubeHooks,
|
||||
pluginInfo,
|
||||
peertubeHelpersFactory: _ => this.buildPeerTubeHelpers(translations)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model'
|
||||
import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model'
|
||||
|
||||
export type RegisterClientOptions = {
|
||||
registerHook: (options: RegisterClientHookOptions) => void
|
||||
|
||||
registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void
|
||||
|
||||
peertubeHelpers: RegisterClientHelpers
|
||||
}
|
||||
|
||||
|
|
|
@ -414,7 +414,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
|||
Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
|
||||
}
|
||||
|
||||
Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated })
|
||||
Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body })
|
||||
} catch (err) {
|
||||
// Force fields we want to update
|
||||
// If the transaction is retried, sequelize will think the object has not changed
|
||||
|
|
|
@ -78,7 +78,10 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor
|
|||
|
||||
userHistory: userHistory ? {
|
||||
currentTime: userHistory.currentTime
|
||||
} : undefined
|
||||
} : undefined,
|
||||
|
||||
// Can be added by external plugins
|
||||
pluginData: (video as any).pluginData
|
||||
}
|
||||
|
||||
if (options) {
|
||||
|
|
|
@ -70,6 +70,9 @@ export const clientActionHookObject = {
|
|||
// Fired when a user click on 'View x replies' and they're loaded
|
||||
'action:video-watch.video-thread-replies.loaded': true,
|
||||
|
||||
// Fired when the video edit page (upload, URL/torrent import, update) is being initialized
|
||||
'action:video-edit.init': true,
|
||||
|
||||
// Fired when the login page is being initialized
|
||||
'action:login.init': true,
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ export * from './plugin-video-privacy-manager.model'
|
|||
export * from './plugin.type'
|
||||
export * from './public-server.setting'
|
||||
export * from './register-client-hook.model'
|
||||
export * from './register-client-form-field.model'
|
||||
export * from './register-server-hook.model'
|
||||
export * from './register-server-setting.model'
|
||||
export * from './server-hook.model'
|
||||
|
|
|
@ -1 +1 @@
|
|||
export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login' | 'embed'
|
||||
export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login' | 'embed' | 'video-edit'
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export interface RegisterClientFormFieldOptions {
|
||||
name: string
|
||||
label: string
|
||||
type: 'input' | 'input-checkbox' | 'input-textarea' | 'markdown-text' | 'markdown-enhanced'
|
||||
|
||||
// Default setting value
|
||||
default?: string | boolean
|
||||
}
|
||||
|
||||
export interface RegisterClientVideoFieldOptions {
|
||||
type: 'import-url' | 'import-torrent' | 'update' | 'upload'
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
export interface RegisterServerSettingOptions {
|
||||
name: string
|
||||
label: string
|
||||
type: 'input' | 'input-checkbox' | 'input-textarea' | 'markdown-text' | 'markdown-enhanced'
|
||||
import { RegisterClientFormFieldOptions } from './register-client-form-field.model'
|
||||
|
||||
export interface RegisterServerSettingOptions extends RegisterClientFormFieldOptions {
|
||||
// If the setting is not private, anyone can view its value (client code included)
|
||||
// If the setting is private, only server-side hooks can access it
|
||||
// Mainly used by the PeerTube client to get admin config
|
||||
private: boolean
|
||||
|
||||
// Default setting value
|
||||
default?: string | boolean
|
||||
}
|
||||
|
||||
export interface RegisteredServerSettings {
|
||||
|
|
|
@ -19,4 +19,6 @@ export interface VideoUpdate {
|
|||
previewfile?: Blob
|
||||
scheduleUpdate?: VideoScheduleUpdate
|
||||
originallyPublishedAt?: Date | string
|
||||
|
||||
pluginData?: any
|
||||
}
|
||||
|
|
|
@ -53,6 +53,8 @@ export interface Video {
|
|||
userHistory?: {
|
||||
currentTime: number
|
||||
}
|
||||
|
||||
pluginData?: any
|
||||
}
|
||||
|
||||
export interface VideoDetails extends Video {
|
||||
|
|
Loading…
Reference in New Issue