mirror of https://github.com/Chocobozzz/PeerTube
Add client hooks
parent
587568e1cc
commit
93cae47925
|
@ -68,7 +68,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
|
|||
switchMap(res => from(res.data)),
|
||||
concatMap(videoChannel => {
|
||||
return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort)
|
||||
.pipe(map(data => ({ videoChannel, videos: data.videos })))
|
||||
.pipe(map(data => ({ videoChannel, videos: data.data })))
|
||||
})
|
||||
)
|
||||
.subscribe(({ videoChannel, videos }) => {
|
||||
|
|
|
@ -69,8 +69,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
|
|||
return this.videoService
|
||||
.getAccountVideos(this.account, newPagination, this.sort)
|
||||
.pipe(
|
||||
tap(({ totalVideos }) => {
|
||||
this.titlePage = this.i18n('Published {{totalVideos}} videos', { totalVideos })
|
||||
tap(({ total }) => {
|
||||
this.titlePage = this.i18n('Published {{total}} videos', { total })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -131,9 +131,9 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
|
|||
|
||||
private loadElements () {
|
||||
this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination)
|
||||
.subscribe(({ totalVideos, videos }) => {
|
||||
this.videos = this.videos.concat(videos)
|
||||
this.pagination.totalItems = totalVideos
|
||||
.subscribe(({ total, data }) => {
|
||||
this.videos = this.videos.concat(data)
|
||||
this.pagination.totalItems = total
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -71,8 +71,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
|
|||
return this.videoService
|
||||
.getVideoChannelVideos(this.videoChannel, newPagination, this.sort)
|
||||
.pipe(
|
||||
tap(({ totalVideos }) => {
|
||||
this.titlePage = this.i18n('Published {{totalVideos}} videos', { totalVideos })
|
||||
tap(({ total }) => {
|
||||
this.titlePage = this.i18n('Published {{total}} videos', { total })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
|||
import { fromEvent } from 'rxjs'
|
||||
import { ViewportScroller } from '@angular/common'
|
||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
|
@ -33,7 +34,8 @@ export class AppComponent implements OnInit {
|
|||
private redirectService: RedirectService,
|
||||
private screenService: ScreenService,
|
||||
private hotkeysService: HotkeysService,
|
||||
private themeService: ThemeService
|
||||
private themeService: ThemeService,
|
||||
private hooks: HooksService
|
||||
) { }
|
||||
|
||||
get serverVersion () {
|
||||
|
@ -206,7 +208,7 @@ export class AppComponent implements OnInit {
|
|||
|
||||
await this.pluginService.loadPluginsByScope('common')
|
||||
|
||||
this.pluginService.runHook('action:application.loaded')
|
||||
this.hooks.runAction('action:application.init')
|
||||
}
|
||||
|
||||
private initHotkeys () {
|
||||
|
|
|
@ -22,6 +22,7 @@ import { UserNotificationSocket } from '@app/core/notification/user-notification
|
|||
import { ServerConfigResolver } from './routing/server-config-resolver.service'
|
||||
import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service'
|
||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -63,6 +64,7 @@ import { PluginService } from '@app/core/plugins/plugin.service'
|
|||
UnloggedGuard,
|
||||
|
||||
PluginService,
|
||||
HooksService,
|
||||
|
||||
RedirectService,
|
||||
Notifier,
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||
import { ClientActionHookName, ClientFilterHookName } from '@shared/models/plugins/client-hook.model'
|
||||
import { from, Observable } from 'rxjs'
|
||||
import { mergeMap, switchMap } from 'rxjs/operators'
|
||||
import { ServerService } from '@app/core/server'
|
||||
import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
|
||||
|
||||
type RawFunction<U, T> = (params: U) => T
|
||||
type ObservableFunction<U, T> = RawFunction<U, Observable<T>>
|
||||
|
||||
@Injectable()
|
||||
export class HooksService {
|
||||
constructor (
|
||||
private server: ServerService,
|
||||
private pluginService: PluginService
|
||||
) { }
|
||||
|
||||
wrapObject<T, U extends ClientFilterHookName> (result: T, hookName: U) {
|
||||
return this.pluginService.runHook(hookName, result)
|
||||
}
|
||||
|
||||
wrapObsFun
|
||||
<P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName>
|
||||
(fun: ObservableFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
|
||||
return from(this.pluginService.ensurePluginsAreLoaded(scope))
|
||||
.pipe(
|
||||
mergeMap(() => this.wrapObject(params, hookParamName)),
|
||||
switchMap(params => fun(params)),
|
||||
mergeMap(result => this.pluginService.runHook(hookResultName, result, params))
|
||||
)
|
||||
}
|
||||
|
||||
async wrapFun<U, T, V extends ClientFilterHookName> (fun: RawFunction<U, T>, params: U, hookName: V) {
|
||||
const result = fun(params)
|
||||
|
||||
return this.pluginService.runHook(hookName, result, params)
|
||||
}
|
||||
|
||||
runAction<T, U extends ClientActionHookName> (hookName: U, params?: T) {
|
||||
this.pluginService.runHook(hookName, params)
|
||||
.catch((err: any) => console.error('Fatal hook error.', { err }))
|
||||
}
|
||||
}
|
|
@ -3,11 +3,13 @@ import { Router } from '@angular/router'
|
|||
import { ServerConfigPlugin } from '@shared/models'
|
||||
import { ServerService } from '@app/core/server/server.service'
|
||||
import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
|
||||
import { PluginScope } from '@shared/models/plugins/plugin-scope.type'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model'
|
||||
import { ReplaySubject } from 'rxjs'
|
||||
import { first, shareReplay } from 'rxjs/operators'
|
||||
import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
|
||||
import { ClientHook, ClientHookName } from '@shared/models/plugins/client-hook.model'
|
||||
import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
|
||||
|
||||
interface HookStructValue extends RegisterHookOptions {
|
||||
plugin: ServerConfigPlugin
|
||||
|
@ -21,14 +23,18 @@ type PluginInfo = {
|
|||
}
|
||||
|
||||
@Injectable()
|
||||
export class PluginService {
|
||||
pluginsLoaded = new ReplaySubject<boolean>(1)
|
||||
export class PluginService implements ClientHook {
|
||||
pluginsBuilt = new ReplaySubject<boolean>(1)
|
||||
|
||||
pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
|
||||
common: new ReplaySubject<boolean>(1),
|
||||
'video-watch': new ReplaySubject<boolean>(1)
|
||||
}
|
||||
|
||||
private plugins: ServerConfigPlugin[] = []
|
||||
private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
|
||||
private loadedPlugins: { [ name: string ]: boolean } = {}
|
||||
private loadedScripts: { [ script: string ]: boolean } = {}
|
||||
private loadedScopes: PluginScope[] = []
|
||||
private loadedScopes: PluginClientScope[] = []
|
||||
|
||||
private hooks: { [ name: string ]: HookStructValue[] } = {}
|
||||
|
||||
|
@ -45,12 +51,18 @@ export class PluginService {
|
|||
|
||||
this.buildScopeStruct()
|
||||
|
||||
this.pluginsLoaded.next(true)
|
||||
this.pluginsBuilt.next(true)
|
||||
})
|
||||
}
|
||||
|
||||
ensurePluginsAreLoaded () {
|
||||
return this.pluginsLoaded.asObservable()
|
||||
ensurePluginsAreBuilt () {
|
||||
return this.pluginsBuilt.asObservable()
|
||||
.pipe(first(), shareReplay())
|
||||
.toPromise()
|
||||
}
|
||||
|
||||
ensurePluginsAreLoaded (scope: PluginClientScope) {
|
||||
return this.pluginsLoaded[scope].asObservable()
|
||||
.pipe(first(), shareReplay())
|
||||
.toPromise()
|
||||
}
|
||||
|
@ -90,9 +102,9 @@ export class PluginService {
|
|||
}
|
||||
}
|
||||
|
||||
async loadPluginsByScope (scope: PluginScope, isReload = false) {
|
||||
async loadPluginsByScope (scope: PluginClientScope, isReload = false) {
|
||||
try {
|
||||
await this.ensurePluginsAreLoaded()
|
||||
await this.ensurePluginsAreBuilt()
|
||||
|
||||
if (!isReload) this.loadedScopes.push(scope)
|
||||
|
||||
|
@ -111,32 +123,24 @@ export class PluginService {
|
|||
}
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
this.pluginsLoaded[scope].next(true)
|
||||
} catch (err) {
|
||||
console.error('Cannot load plugins by scope %s.', scope, err)
|
||||
}
|
||||
}
|
||||
|
||||
async runHook (hookName: string, param?: any) {
|
||||
let result = param
|
||||
async runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
|
||||
if (!this.hooks[hookName]) return Promise.resolve(result)
|
||||
|
||||
if (!this.hooks[hookName]) return result
|
||||
|
||||
const wait = hookName.startsWith('static:')
|
||||
const hookType = getHookType(hookName)
|
||||
|
||||
for (const hook of this.hooks[hookName]) {
|
||||
try {
|
||||
const p = hook.handler(param)
|
||||
console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
|
||||
|
||||
if (wait) {
|
||||
result = await p
|
||||
} else if (p.catch) {
|
||||
p.catch((err: Error) => {
|
||||
console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err)
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err)
|
||||
}
|
||||
result = await internalRunHook(hook.handler, hookType, result, params, err => {
|
||||
console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -10,6 +10,7 @@ import { AdvancedSearch } from '@app/search/advanced-search.model'
|
|||
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
|
||||
import { immutableAssign } from '@app/shared/misc/utils'
|
||||
import { Video } from '@app/shared/video/video.model'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-search',
|
||||
|
@ -41,7 +42,8 @@ export class SearchComponent implements OnInit, OnDestroy {
|
|||
private metaService: MetaService,
|
||||
private notifier: Notifier,
|
||||
private searchService: SearchService,
|
||||
private authService: AuthService
|
||||
private authService: AuthService,
|
||||
private hooks: HooksService
|
||||
) { }
|
||||
|
||||
get user () {
|
||||
|
@ -93,18 +95,18 @@ export class SearchComponent implements OnInit, OnDestroy {
|
|||
|
||||
search () {
|
||||
forkJoin([
|
||||
this.searchService.searchVideos(this.currentSearch, this.pagination, this.advancedSearch),
|
||||
this.searchService.searchVideoChannels(this.currentSearch, immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }))
|
||||
this.getVideosObs(),
|
||||
this.getVideoChannelObs()
|
||||
])
|
||||
.subscribe(
|
||||
([ videosResult, videoChannelsResult ]) => {
|
||||
this.results = this.results
|
||||
.concat(videoChannelsResult.data)
|
||||
.concat(videosResult.videos)
|
||||
this.pagination.totalItems = videosResult.totalVideos + videoChannelsResult.total
|
||||
.concat(videosResult.data)
|
||||
this.pagination.totalItems = videosResult.total + videoChannelsResult.total
|
||||
|
||||
// Focus on channels if there are no enough videos
|
||||
if (this.firstSearch === true && videosResult.videos.length < this.pagination.itemsPerPage) {
|
||||
if (this.firstSearch === true && videosResult.data.length < this.pagination.itemsPerPage) {
|
||||
this.resetPagination()
|
||||
this.firstSearch = false
|
||||
|
||||
|
@ -117,7 +119,6 @@ export class SearchComponent implements OnInit, OnDestroy {
|
|||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
onNearOfBottom () {
|
||||
|
@ -163,4 +164,35 @@ export class SearchComponent implements OnInit, OnDestroy {
|
|||
queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search })
|
||||
})
|
||||
}
|
||||
|
||||
private getVideosObs () {
|
||||
const params = {
|
||||
search: this.currentSearch,
|
||||
componentPagination: this.pagination,
|
||||
advancedSearch: this.advancedSearch
|
||||
}
|
||||
|
||||
return this.hooks.wrapObsFun(
|
||||
this.searchService.searchVideos.bind(this.searchService),
|
||||
params,
|
||||
'common',
|
||||
'filter:api.search.videos.list.params',
|
||||
'filter:api.search.videos.list.result'
|
||||
)
|
||||
}
|
||||
|
||||
private getVideoChannelObs () {
|
||||
const params = {
|
||||
search: this.currentSearch,
|
||||
componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage })
|
||||
}
|
||||
|
||||
return this.hooks.wrapObsFun(
|
||||
this.searchService.searchVideoChannels.bind(this.searchService),
|
||||
params,
|
||||
'common',
|
||||
'filter:api.search.video-channels.list.params',
|
||||
'filter:api.search.video-channels.list.result'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,14 @@ export class SearchService {
|
|||
private videoService: VideoService
|
||||
) {}
|
||||
|
||||
searchVideos (
|
||||
searchVideos (parameters: {
|
||||
search: string,
|
||||
componentPagination: ComponentPagination,
|
||||
advancedSearch: AdvancedSearch
|
||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
const url = SearchService.BASE_SEARCH_URL + 'videos'
|
||||
}): Observable<ResultList<Video>> {
|
||||
const { search, componentPagination, advancedSearch } = parameters
|
||||
|
||||
const url = SearchService.BASE_SEARCH_URL + 'videos'
|
||||
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -48,12 +49,13 @@ export class SearchService {
|
|||
)
|
||||
}
|
||||
|
||||
searchVideoChannels (
|
||||
searchVideoChannels (parameters: {
|
||||
search: string,
|
||||
componentPagination: ComponentPagination
|
||||
): Observable<{ data: VideoChannel[], total: number }> {
|
||||
const url = SearchService.BASE_SEARCH_URL + 'video-channels'
|
||||
}): Observable<ResultList<VideoChannel>> {
|
||||
const { search, componentPagination } = parameters
|
||||
|
||||
const url = SearchService.BASE_SEARCH_URL + 'video-channels'
|
||||
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
|
|
@ -45,7 +45,7 @@ export class OverviewService {
|
|||
of(object.videos)
|
||||
.pipe(
|
||||
switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })),
|
||||
map(result => result.videos),
|
||||
map(result => result.data),
|
||||
tap(videos => {
|
||||
videosOverviewResult[key].push(immutableAssign(object, { videos }))
|
||||
})
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Notifier, ServerService } from '@app/core'
|
|||
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
|
||||
import { ResultList } from '@shared/models'
|
||||
|
||||
enum GroupDate {
|
||||
UNKNOWN = 0,
|
||||
|
@ -73,7 +74,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
|
|||
private groupedDateLabels: { [id in GroupDate]: string }
|
||||
private groupedDates: { [id: number]: GroupDate } = {}
|
||||
|
||||
abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
|
||||
abstract getVideosObservable (page: number): Observable<ResultList<Video>>
|
||||
|
||||
abstract generateSyndicationList (): void
|
||||
|
||||
|
@ -138,12 +139,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
|
|||
}
|
||||
|
||||
loadMoreVideos () {
|
||||
const observable = this.getVideosObservable(this.pagination.currentPage)
|
||||
|
||||
observable.subscribe(
|
||||
({ videos, totalVideos }) => {
|
||||
this.pagination.totalItems = totalVideos
|
||||
this.videos = this.videos.concat(videos)
|
||||
this.getVideosObservable(this.pagination.currentPage).subscribe(
|
||||
({ data, total }) => {
|
||||
this.pagination.totalItems = total
|
||||
this.videos = this.videos.concat(data)
|
||||
|
||||
if (this.groupByDate) this.buildGroupedDateLabels()
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ export interface VideosProvider {
|
|||
filter?: VideoFilter,
|
||||
categoryOneOf?: number,
|
||||
languageOneOf?: string[]
|
||||
}): Observable<{ videos: Video[], totalVideos: number }>
|
||||
}): Observable<ResultList<Video>>
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -65,11 +65,11 @@ export class VideoService implements VideosProvider {
|
|||
return VideoService.BASE_VIDEO_URL + uuid + '/watching'
|
||||
}
|
||||
|
||||
getVideo (uuid: string): Observable<VideoDetails> {
|
||||
getVideo (options: { videoId: string }): Observable<VideoDetails> {
|
||||
return this.serverService.localeObservable
|
||||
.pipe(
|
||||
switchMap(translations => {
|
||||
return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
|
||||
return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + options.videoId)
|
||||
.pipe(map(videoHash => ({ videoHash, translations })))
|
||||
}),
|
||||
map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)),
|
||||
|
@ -123,7 +123,7 @@ export class VideoService implements VideosProvider {
|
|||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||
}
|
||||
|
||||
getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<ResultList<Video>> {
|
||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -141,7 +141,7 @@ export class VideoService implements VideosProvider {
|
|||
account: Account,
|
||||
videoPagination: ComponentPagination,
|
||||
sort: VideoSortField
|
||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
): Observable<ResultList<Video>> {
|
||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -159,7 +159,7 @@ export class VideoService implements VideosProvider {
|
|||
videoChannel: VideoChannel,
|
||||
videoPagination: ComponentPagination,
|
||||
sort: VideoSortField
|
||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
): Observable<ResultList<Video>> {
|
||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -176,7 +176,7 @@ export class VideoService implements VideosProvider {
|
|||
getPlaylistVideos (
|
||||
videoPlaylistId: number | string,
|
||||
videoPagination: ComponentPagination
|
||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
): Observable<ResultList<Video>> {
|
||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -190,10 +190,11 @@ export class VideoService implements VideosProvider {
|
|||
)
|
||||
}
|
||||
|
||||
getUserSubscriptionVideos (
|
||||
getUserSubscriptionVideos (parameters: {
|
||||
videoPagination: ComponentPagination,
|
||||
sort: VideoSortField
|
||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
}): Observable<ResultList<Video>> {
|
||||
const { videoPagination, sort } = parameters
|
||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -213,7 +214,7 @@ export class VideoService implements VideosProvider {
|
|||
filter?: VideoFilter,
|
||||
categoryOneOf?: number,
|
||||
languageOneOf?: string[]
|
||||
}): Observable<{ videos: Video[], totalVideos: number }> {
|
||||
}): Observable<ResultList<Video>> {
|
||||
const { videoPagination, sort, filter, categoryOneOf, languageOneOf } = parameters
|
||||
|
||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||
|
@ -344,7 +345,7 @@ export class VideoService implements VideosProvider {
|
|||
videos.push(new Video(videoJson, translations))
|
||||
}
|
||||
|
||||
return { videos, totalVideos }
|
||||
return { total: totalVideos, data: videos }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
|||
import { VideoSortField } from '@app/shared/video/sort-field.type'
|
||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ResultList } from '@shared/models'
|
||||
|
||||
export type SelectionType = { [ id: number ]: boolean }
|
||||
|
||||
|
@ -33,7 +34,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
|
|||
@Input() pagination: ComponentPagination
|
||||
@Input() titlePage: string
|
||||
@Input() miniatureDisplayOptions: MiniatureDisplayOptions
|
||||
@Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<{ videos: Video[], totalVideos: number }>
|
||||
@Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>>
|
||||
@ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective>
|
||||
|
||||
@Output() selectionChange = new EventEmitter<SelectionType>()
|
||||
|
|
|
@ -18,7 +18,7 @@ export class VideoUpdateResolver implements Resolve<any> {
|
|||
resolve (route: ActivatedRouteSnapshot) {
|
||||
const uuid: string = route.params[ 'uuid' ]
|
||||
|
||||
return this.videoService.getVideo(uuid)
|
||||
return this.videoService.getVideo({ videoId: uuid })
|
||||
.pipe(
|
||||
switchMap(video => {
|
||||
return forkJoin([
|
||||
|
|
|
@ -48,11 +48,13 @@ export class VideoCommentService {
|
|||
)
|
||||
}
|
||||
|
||||
getVideoCommentThreads (
|
||||
getVideoCommentThreads (parameters: {
|
||||
videoId: number | string,
|
||||
componentPagination: ComponentPagination,
|
||||
sort: VideoSortField
|
||||
): Observable<{ comments: VideoComment[], totalComments: number}> {
|
||||
}): Observable<{ comments: VideoComment[], totalComments: number}> {
|
||||
const { videoId, componentPagination, sort } = parameters
|
||||
|
||||
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
|
@ -67,7 +69,11 @@ export class VideoCommentService {
|
|||
)
|
||||
}
|
||||
|
||||
getVideoThreadComments (videoId: number | string, threadId: number): Observable<VideoCommentThreadTree> {
|
||||
getVideoThreadComments (parameters: {
|
||||
videoId: number | string,
|
||||
threadId: number
|
||||
}): Observable<VideoCommentThreadTree> {
|
||||
const { videoId, threadId } = parameters
|
||||
const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}`
|
||||
|
||||
return this.authHttp
|
||||
|
|
|
@ -12,6 +12,7 @@ import { VideoComment } from './video-comment.model'
|
|||
import { VideoCommentService } from './video-comment.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { Syndication } from '@app/shared/video/syndication.model'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-comments',
|
||||
|
@ -45,7 +46,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
|||
private confirmService: ConfirmService,
|
||||
private videoCommentService: VideoCommentService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private i18n: I18n
|
||||
private i18n: I18n,
|
||||
private hooks: HooksService
|
||||
) {}
|
||||
|
||||
ngOnInit () {
|
||||
|
@ -73,8 +75,20 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
|||
viewReplies (commentId: number, highlightThread = false) {
|
||||
this.threadLoading[commentId] = true
|
||||
|
||||
this.videoCommentService.getVideoThreadComments(this.video.id, commentId)
|
||||
.subscribe(
|
||||
const params = {
|
||||
videoId: this.video.id,
|
||||
threadId: commentId
|
||||
}
|
||||
|
||||
const obs = this.hooks.wrapObsFun(
|
||||
this.videoCommentService.getVideoThreadComments.bind(this.videoCommentService),
|
||||
params,
|
||||
'video-watch',
|
||||
'filter:api.video-watch.video-thread-replies.list.params',
|
||||
'filter:api.video-watch.video-thread-replies.list.result'
|
||||
)
|
||||
|
||||
obs.subscribe(
|
||||
res => {
|
||||
this.threadComments[commentId] = res
|
||||
this.threadLoading[commentId] = false
|
||||
|
@ -91,16 +105,29 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
|||
)
|
||||
}
|
||||
|
||||
loadMoreComments () {
|
||||
this.videoCommentService.getVideoCommentThreads(this.video.id, this.componentPagination, this.sort)
|
||||
.subscribe(
|
||||
res => {
|
||||
this.comments = this.comments.concat(res.comments)
|
||||
this.componentPagination.totalItems = res.totalComments
|
||||
},
|
||||
loadMoreThreads () {
|
||||
const params = {
|
||||
videoId: this.video.id,
|
||||
componentPagination: this.componentPagination,
|
||||
sort: this.sort
|
||||
}
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
const obs = this.hooks.wrapObsFun(
|
||||
this.videoCommentService.getVideoCommentThreads.bind(this.videoCommentService),
|
||||
params,
|
||||
'video-watch',
|
||||
'filter:api.video-watch.video-threads.list.params',
|
||||
'filter:api.video-watch.video-threads.list.result'
|
||||
)
|
||||
|
||||
obs.subscribe(
|
||||
res => {
|
||||
this.comments = this.comments.concat(res.comments)
|
||||
this.componentPagination.totalItems = res.totalComments
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
|
||||
onCommentThreadCreated (comment: VideoComment) {
|
||||
|
@ -169,7 +196,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.componentPagination.currentPage++
|
||||
|
||||
if (hasMoreItems(this.componentPagination)) {
|
||||
this.loadMoreComments()
|
||||
this.loadMoreThreads()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,7 +224,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid)
|
||||
|
||||
this.loadMoreComments()
|
||||
this.loadMoreThreads()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,11 +66,11 @@ export class VideoWatchPlaylistComponent {
|
|||
|
||||
loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) {
|
||||
this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination)
|
||||
.subscribe(({ totalVideos, videos }) => {
|
||||
this.playlistVideos = this.playlistVideos.concat(videos)
|
||||
this.playlistPagination.totalItems = totalVideos
|
||||
.subscribe(({ total, data }) => {
|
||||
this.playlistVideos = this.playlistVideos.concat(data)
|
||||
this.playlistPagination.totalItems = total
|
||||
|
||||
if (totalVideos === 0) {
|
||||
if (total === 0) {
|
||||
this.noPlaylistVideos = true
|
||||
return
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils'
|
|||
import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
|
||||
import { getStoredTheater } from '../../../assets/player/peertube-player-local-storage'
|
||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-watch',
|
||||
|
@ -93,6 +94,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
private videoCaptionService: VideoCaptionService,
|
||||
private i18n: I18n,
|
||||
private hotkeysService: HotkeysService,
|
||||
private hooks: HooksService,
|
||||
@Inject(LOCALE_ID) private localeId: string
|
||||
) {}
|
||||
|
||||
|
@ -131,7 +133,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.theaterEnabled = getStoredTheater()
|
||||
|
||||
this.pluginService.runHook('action:video-watch.loaded')
|
||||
this.hooks.runAction('action:video-watch.init')
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
|
@ -246,9 +248,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (this.player) this.player.pause()
|
||||
|
||||
const videoObs = this.hooks.wrapObsFun(
|
||||
this.videoService.getVideo.bind(this.videoService),
|
||||
{ videoId },
|
||||
'video-watch',
|
||||
'filter:api.video-watch.video.get.params',
|
||||
'filter:api.video-watch.video.get.result'
|
||||
)
|
||||
|
||||
// Video did change
|
||||
forkJoin(
|
||||
this.videoService.getVideo(videoId),
|
||||
videoObs,
|
||||
this.videoCaptionService.listCaptions(videoId)
|
||||
)
|
||||
.pipe(
|
||||
|
@ -486,6 +496,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.setOpenGraphTags()
|
||||
this.checkUserRating()
|
||||
|
||||
this.hooks.runAction('action:video-watch.video.loaded')
|
||||
}
|
||||
|
||||
private setRating (nextRating: UserVideoRateType) {
|
||||
|
|
|
@ -33,20 +33,24 @@ export class RecentVideosRecommendationService implements RecommendationService
|
|||
private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> {
|
||||
const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 }
|
||||
const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' })
|
||||
.pipe(map(v => v.videos))
|
||||
.pipe(map(v => v.data))
|
||||
|
||||
if (!recommendation.tags || recommendation.tags.length === 0) return defaultSubscription
|
||||
|
||||
return this.searchService.searchVideos('',
|
||||
pagination,
|
||||
new AdvancedSearch({ tagsOneOf: recommendation.tags.join(','), sort: '-createdAt' })
|
||||
).pipe(
|
||||
map(v => v.videos),
|
||||
switchMap(videos => {
|
||||
if (videos.length <= 1) return defaultSubscription
|
||||
const params = {
|
||||
search: '',
|
||||
componentPagination: pagination,
|
||||
advancedSearch: new AdvancedSearch({ tagsOneOf: recommendation.tags.join(','), sort: '-createdAt' })
|
||||
}
|
||||
|
||||
return of(videos)
|
||||
})
|
||||
)
|
||||
return this.searchService.searchVideos(params)
|
||||
.pipe(
|
||||
map(v => v.data),
|
||||
switchMap(videos => {
|
||||
if (videos.length <= 1) return defaultSubscription
|
||||
|
||||
return of(videos)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
|||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { UserRight } from '../../../../../shared/models/users'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-local',
|
||||
|
@ -31,7 +32,8 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
|
|||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected screenService: ScreenService,
|
||||
private videoService: VideoService
|
||||
private videoService: VideoService,
|
||||
private hooks: HooksService
|
||||
) {
|
||||
super()
|
||||
|
||||
|
@ -55,14 +57,21 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
|
|||
|
||||
getVideosObservable (page: number) {
|
||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||
|
||||
return this.videoService.getVideos({
|
||||
const params = {
|
||||
videoPagination: newPagination,
|
||||
sort: this.sort,
|
||||
filter: this.filter,
|
||||
categoryOneOf: this.categoryOneOf,
|
||||
languageOneOf: this.languageOneOf
|
||||
})
|
||||
}
|
||||
|
||||
return this.hooks.wrapObsFun(
|
||||
this.videoService.getVideos.bind(this.videoService),
|
||||
params,
|
||||
'common',
|
||||
'filter:api.videos.list.local.params',
|
||||
'filter:api.videos.list.local.result'
|
||||
)
|
||||
}
|
||||
|
||||
generateSyndicationList () {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-recently-added',
|
||||
|
@ -29,7 +30,8 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
|
|||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected screenService: ScreenService,
|
||||
private videoService: VideoService
|
||||
private videoService: VideoService,
|
||||
private hooks: HooksService
|
||||
) {
|
||||
super()
|
||||
|
||||
|
@ -48,14 +50,20 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
|
|||
|
||||
getVideosObservable (page: number) {
|
||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||
|
||||
return this.videoService.getVideos({
|
||||
const params = {
|
||||
videoPagination: newPagination,
|
||||
sort: this.sort,
|
||||
filter: undefined,
|
||||
categoryOneOf: this.categoryOneOf,
|
||||
languageOneOf: this.languageOneOf
|
||||
})
|
||||
}
|
||||
|
||||
return this.hooks.wrapObsFun(
|
||||
this.videoService.getVideos.bind(this.videoService),
|
||||
params,
|
||||
'common',
|
||||
'filter:api.videos.list.recently-added.params',
|
||||
'filter:api.videos.list.recently-added.result'
|
||||
)
|
||||
}
|
||||
|
||||
generateSyndicationList () {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
|
|||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-trending',
|
||||
|
@ -28,7 +29,8 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
|
|||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected screenService: ScreenService,
|
||||
private videoService: VideoService
|
||||
private videoService: VideoService,
|
||||
private hooks: HooksService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
@ -61,13 +63,20 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
|
|||
|
||||
getVideosObservable (page: number) {
|
||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||
return this.videoService.getVideos({
|
||||
const params = {
|
||||
videoPagination: newPagination,
|
||||
sort: this.sort,
|
||||
filter: undefined,
|
||||
categoryOneOf: this.categoryOneOf,
|
||||
languageOneOf: this.languageOneOf
|
||||
})
|
||||
}
|
||||
|
||||
return this.hooks.wrapObsFun(
|
||||
this.videoService.getVideos.bind(this.videoService),
|
||||
params,
|
||||
'common',
|
||||
'filter:api.videos.list.trending.params',
|
||||
'filter:api.videos.list.trending.result'
|
||||
)
|
||||
}
|
||||
|
||||
generateSyndicationList () {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
|||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-user-subscriptions',
|
||||
|
@ -29,7 +30,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
|
|||
protected notifier: Notifier,
|
||||
protected authService: AuthService,
|
||||
protected screenService: ScreenService,
|
||||
private videoService: VideoService
|
||||
private videoService: VideoService,
|
||||
private hooks: HooksService
|
||||
) {
|
||||
super()
|
||||
|
||||
|
@ -46,8 +48,18 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
|
|||
|
||||
getVideosObservable (page: number) {
|
||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||
const params = {
|
||||
videoPagination: newPagination,
|
||||
sort: this.sort
|
||||
}
|
||||
|
||||
return this.videoService.getUserSubscriptionVideos(newPagination, this.sort)
|
||||
return this.hooks.wrapObsFun(
|
||||
this.videoService.getUserSubscriptionVideos.bind(this.videoService),
|
||||
params,
|
||||
'common',
|
||||
'filter:api.videos.list.user-subscriptions.params',
|
||||
'filter:api.videos.list.user-subscriptions.result'
|
||||
)
|
||||
}
|
||||
|
||||
generateSyndicationList () {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"noImplicitThis": true,
|
||||
"suppressImplicitAnyIndexErrors":true,
|
||||
"alwaysStrict": true,
|
||||
"strictBindCallApply": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
|
|
|
@ -29,7 +29,7 @@ async function internalRunHook <T> (handler: Function, hookType: HookType, resul
|
|||
}
|
||||
|
||||
if (hookType === HookType.ACTION) {
|
||||
if (isCatchable(p)) p.catch(err => onError(err))
|
||||
if (isCatchable(p)) p.catch((err: any) => onError(err))
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
export type ClientFilterHookName =
|
||||
'filter:api.videos.list.trending.params' |
|
||||
'filter:api.videos.list.trending.result' |
|
||||
|
||||
'filter:api.videos.list.local.params' |
|
||||
'filter:api.videos.list.local.result' |
|
||||
|
||||
'filter:api.videos.list.recently-added.params' |
|
||||
'filter:api.videos.list.recently-added.result' |
|
||||
|
||||
'filter:api.videos.list.user-subscriptions.params' |
|
||||
'filter:api.videos.list.user-subscriptions.result' |
|
||||
|
||||
'filter:api.video-watch.video.get.params' |
|
||||
'filter:api.video-watch.video.get.result' |
|
||||
|
||||
'filter:api.video-watch.video-threads.list.params' |
|
||||
'filter:api.video-watch.video-threads.list.result' |
|
||||
|
||||
'filter:api.video-watch.video-thread-replies.list.params' |
|
||||
'filter:api.video-watch.video-thread-replies.list.result' |
|
||||
|
||||
'filter:api.search.videos.list.params' |
|
||||
'filter:api.search.videos.list.result' |
|
||||
'filter:api.search.video-channels.list.params' |
|
||||
'filter:api.search.video-channels.list.result'
|
||||
|
||||
export type ClientActionHookName =
|
||||
'action:application.init' |
|
||||
|
||||
'action:video-watch.init' |
|
||||
|
||||
'action:video-watch.video.loaded'
|
||||
|
||||
export type ClientHookName = ClientActionHookName | ClientFilterHookName
|
||||
|
||||
export interface ClientHook {
|
||||
runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T>
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export type PluginClientScope = 'common' | 'video-watch'
|
|
@ -1 +0,0 @@
|
|||
export type PluginScope = 'common' | 'video-watch'
|
|
@ -30,5 +30,5 @@ export type ServerActionHookName =
|
|||
export type ServerHookName = ServerFilterHookName | ServerActionHookName
|
||||
|
||||
export interface ServerHook {
|
||||
runHook (hookName: ServerHookName, params?: any)
|
||||
runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T>
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||
import { ClientScript } from '../plugins/plugin-package-json.model'
|
||||
import { PluginClientScope } from '../plugins/plugin-scope.type'
|
||||
|
||||
export interface ServerConfigPlugin {
|
||||
name: string
|
||||
version: string
|
||||
description: string
|
||||
clientScripts: { [name: string]: ClientScript }
|
||||
clientScripts: { [name in PluginClientScope]: ClientScript }
|
||||
}
|
||||
|
||||
export interface ServerConfigTheme extends ServerConfigPlugin {
|
||||
|
|
Loading…
Reference in New Issue