diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-22 15:40:13 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | 93cae47925e4dd68b7d34a41927b2740b4fab1b4 (patch) | |
tree | f649ab49fab1886b434e164591990cc99b234466 | |
parent | 587568e1cc0e33c023c1ac62dd28fef313285250 (diff) | |
download | PeerTube-93cae47925e4dd68b7d34a41927b2740b4fab1b4.tar.gz PeerTube-93cae47925e4dd68b7d34a41927b2740b4fab1b4.tar.zst PeerTube-93cae47925e4dd68b7d34a41927b2740b4fab1b4.zip |
Add client hooks
31 files changed, 341 insertions, 126 deletions
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts index a8d4237e8..4d07d653f 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts | |||
@@ -68,7 +68,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
68 | switchMap(res => from(res.data)), | 68 | switchMap(res => from(res.data)), |
69 | concatMap(videoChannel => { | 69 | concatMap(videoChannel => { |
70 | return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort) | 70 | return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort) |
71 | .pipe(map(data => ({ videoChannel, videos: data.videos }))) | 71 | .pipe(map(data => ({ videoChannel, videos: data.data }))) |
72 | }) | 72 | }) |
73 | ) | 73 | ) |
74 | .subscribe(({ videoChannel, videos }) => { | 74 | .subscribe(({ videoChannel, videos }) => { |
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index 5a99aadce..ac4477c18 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts | |||
@@ -69,8 +69,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
69 | return this.videoService | 69 | return this.videoService |
70 | .getAccountVideos(this.account, newPagination, this.sort) | 70 | .getAccountVideos(this.account, newPagination, this.sort) |
71 | .pipe( | 71 | .pipe( |
72 | tap(({ totalVideos }) => { | 72 | tap(({ total }) => { |
73 | this.titlePage = this.i18n('Published {{totalVideos}} videos', { totalVideos }) | 73 | this.titlePage = this.i18n('Published {{total}} videos', { total }) |
74 | }) | 74 | }) |
75 | ) | 75 | ) |
76 | } | 76 | } |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts index 03f34412c..d5122aeba 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts | |||
@@ -131,9 +131,9 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
131 | 131 | ||
132 | private loadElements () { | 132 | private loadElements () { |
133 | this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) | 133 | this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) |
134 | .subscribe(({ totalVideos, videos }) => { | 134 | .subscribe(({ total, data }) => { |
135 | this.videos = this.videos.concat(videos) | 135 | this.videos = this.videos.concat(data) |
136 | this.pagination.totalItems = totalVideos | 136 | this.pagination.totalItems = total |
137 | }) | 137 | }) |
138 | } | 138 | } |
139 | 139 | ||
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts index 629fd4450..c1dc25aaf 100644 --- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts +++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts | |||
@@ -71,8 +71,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
71 | return this.videoService | 71 | return this.videoService |
72 | .getVideoChannelVideos(this.videoChannel, newPagination, this.sort) | 72 | .getVideoChannelVideos(this.videoChannel, newPagination, this.sort) |
73 | .pipe( | 73 | .pipe( |
74 | tap(({ totalVideos }) => { | 74 | tap(({ total }) => { |
75 | this.titlePage = this.i18n('Published {{totalVideos}} videos', { totalVideos }) | 75 | this.titlePage = this.i18n('Published {{total}} videos', { total }) |
76 | }) | 76 | }) |
77 | ) | 77 | ) |
78 | } | 78 | } |
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 0ebd628fc..bde97c68b 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -10,6 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
10 | import { fromEvent } from 'rxjs' | 10 | import { fromEvent } from 'rxjs' |
11 | import { ViewportScroller } from '@angular/common' | 11 | import { ViewportScroller } from '@angular/common' |
12 | import { PluginService } from '@app/core/plugins/plugin.service' | 12 | import { PluginService } from '@app/core/plugins/plugin.service' |
13 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
13 | 14 | ||
14 | @Component({ | 15 | @Component({ |
15 | selector: 'my-app', | 16 | selector: 'my-app', |
@@ -33,7 +34,8 @@ export class AppComponent implements OnInit { | |||
33 | private redirectService: RedirectService, | 34 | private redirectService: RedirectService, |
34 | private screenService: ScreenService, | 35 | private screenService: ScreenService, |
35 | private hotkeysService: HotkeysService, | 36 | private hotkeysService: HotkeysService, |
36 | private themeService: ThemeService | 37 | private themeService: ThemeService, |
38 | private hooks: HooksService | ||
37 | ) { } | 39 | ) { } |
38 | 40 | ||
39 | get serverVersion () { | 41 | get serverVersion () { |
@@ -206,7 +208,7 @@ export class AppComponent implements OnInit { | |||
206 | 208 | ||
207 | await this.pluginService.loadPluginsByScope('common') | 209 | await this.pluginService.loadPluginsByScope('common') |
208 | 210 | ||
209 | this.pluginService.runHook('action:application.loaded') | 211 | this.hooks.runAction('action:application.init') |
210 | } | 212 | } |
211 | 213 | ||
212 | private initHotkeys () { | 214 | private initHotkeys () { |
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 436c0dfb8..5943af4da 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts | |||
@@ -22,6 +22,7 @@ import { UserNotificationSocket } from '@app/core/notification/user-notification | |||
22 | import { ServerConfigResolver } from './routing/server-config-resolver.service' | 22 | import { ServerConfigResolver } from './routing/server-config-resolver.service' |
23 | import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service' | 23 | import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service' |
24 | import { PluginService } from '@app/core/plugins/plugin.service' | 24 | import { PluginService } from '@app/core/plugins/plugin.service' |
25 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
25 | 26 | ||
26 | @NgModule({ | 27 | @NgModule({ |
27 | imports: [ | 28 | imports: [ |
@@ -63,6 +64,7 @@ import { PluginService } from '@app/core/plugins/plugin.service' | |||
63 | UnloggedGuard, | 64 | UnloggedGuard, |
64 | 65 | ||
65 | PluginService, | 66 | PluginService, |
67 | HooksService, | ||
66 | 68 | ||
67 | RedirectService, | 69 | RedirectService, |
68 | Notifier, | 70 | Notifier, |
diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts new file mode 100644 index 000000000..80c57869c --- /dev/null +++ b/client/src/app/core/plugins/hooks.service.ts | |||
@@ -0,0 +1,44 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { PluginService } from '@app/core/plugins/plugin.service' | ||
3 | import { ClientActionHookName, ClientFilterHookName } from '@shared/models/plugins/client-hook.model' | ||
4 | import { from, Observable } from 'rxjs' | ||
5 | import { mergeMap, switchMap } from 'rxjs/operators' | ||
6 | import { ServerService } from '@app/core/server' | ||
7 | import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type' | ||
8 | |||
9 | type RawFunction<U, T> = (params: U) => T | ||
10 | type ObservableFunction<U, T> = RawFunction<U, Observable<T>> | ||
11 | |||
12 | @Injectable() | ||
13 | export class HooksService { | ||
14 | constructor ( | ||
15 | private server: ServerService, | ||
16 | private pluginService: PluginService | ||
17 | ) { } | ||
18 | |||
19 | wrapObject<T, U extends ClientFilterHookName> (result: T, hookName: U) { | ||
20 | return this.pluginService.runHook(hookName, result) | ||
21 | } | ||
22 | |||
23 | wrapObsFun | ||
24 | <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName> | ||
25 | (fun: ObservableFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) { | ||
26 | return from(this.pluginService.ensurePluginsAreLoaded(scope)) | ||
27 | .pipe( | ||
28 | mergeMap(() => this.wrapObject(params, hookParamName)), | ||
29 | switchMap(params => fun(params)), | ||
30 | mergeMap(result => this.pluginService.runHook(hookResultName, result, params)) | ||
31 | ) | ||
32 | } | ||
33 | |||
34 | async wrapFun<U, T, V extends ClientFilterHookName> (fun: RawFunction<U, T>, params: U, hookName: V) { | ||
35 | const result = fun(params) | ||
36 | |||
37 | return this.pluginService.runHook(hookName, result, params) | ||
38 | } | ||
39 | |||
40 | runAction<T, U extends ClientActionHookName> (hookName: U, params?: T) { | ||
41 | this.pluginService.runHook(hookName, params) | ||
42 | .catch((err: any) => console.error('Fatal hook error.', { err })) | ||
43 | } | ||
44 | } | ||
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index af330c2eb..14310f093 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -3,11 +3,13 @@ import { Router } from '@angular/router' | |||
3 | import { ServerConfigPlugin } from '@shared/models' | 3 | import { ServerConfigPlugin } from '@shared/models' |
4 | import { ServerService } from '@app/core/server/server.service' | 4 | import { ServerService } from '@app/core/server/server.service' |
5 | import { ClientScript } from '@shared/models/plugins/plugin-package-json.model' | 5 | import { ClientScript } from '@shared/models/plugins/plugin-package-json.model' |
6 | import { PluginScope } from '@shared/models/plugins/plugin-scope.type' | ||
7 | import { environment } from '../../../environments/environment' | 6 | import { environment } from '../../../environments/environment' |
8 | import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model' | 7 | import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model' |
9 | import { ReplaySubject } from 'rxjs' | 8 | import { ReplaySubject } from 'rxjs' |
10 | import { first, shareReplay } from 'rxjs/operators' | 9 | import { first, shareReplay } from 'rxjs/operators' |
10 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
11 | import { ClientHook, ClientHookName } from '@shared/models/plugins/client-hook.model' | ||
12 | import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type' | ||
11 | 13 | ||
12 | interface HookStructValue extends RegisterHookOptions { | 14 | interface HookStructValue extends RegisterHookOptions { |
13 | plugin: ServerConfigPlugin | 15 | plugin: ServerConfigPlugin |
@@ -21,14 +23,18 @@ type PluginInfo = { | |||
21 | } | 23 | } |
22 | 24 | ||
23 | @Injectable() | 25 | @Injectable() |
24 | export class PluginService { | 26 | export class PluginService implements ClientHook { |
25 | pluginsLoaded = new ReplaySubject<boolean>(1) | 27 | pluginsBuilt = new ReplaySubject<boolean>(1) |
28 | |||
29 | pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = { | ||
30 | common: new ReplaySubject<boolean>(1), | ||
31 | 'video-watch': new ReplaySubject<boolean>(1) | ||
32 | } | ||
26 | 33 | ||
27 | private plugins: ServerConfigPlugin[] = [] | 34 | private plugins: ServerConfigPlugin[] = [] |
28 | private scopes: { [ scopeName: string ]: PluginInfo[] } = {} | 35 | private scopes: { [ scopeName: string ]: PluginInfo[] } = {} |
29 | private loadedPlugins: { [ name: string ]: boolean } = {} | ||
30 | private loadedScripts: { [ script: string ]: boolean } = {} | 36 | private loadedScripts: { [ script: string ]: boolean } = {} |
31 | private loadedScopes: PluginScope[] = [] | 37 | private loadedScopes: PluginClientScope[] = [] |
32 | 38 | ||
33 | private hooks: { [ name: string ]: HookStructValue[] } = {} | 39 | private hooks: { [ name: string ]: HookStructValue[] } = {} |
34 | 40 | ||
@@ -45,12 +51,18 @@ export class PluginService { | |||
45 | 51 | ||
46 | this.buildScopeStruct() | 52 | this.buildScopeStruct() |
47 | 53 | ||
48 | this.pluginsLoaded.next(true) | 54 | this.pluginsBuilt.next(true) |
49 | }) | 55 | }) |
50 | } | 56 | } |
51 | 57 | ||
52 | ensurePluginsAreLoaded () { | 58 | ensurePluginsAreBuilt () { |
53 | return this.pluginsLoaded.asObservable() | 59 | return this.pluginsBuilt.asObservable() |
60 | .pipe(first(), shareReplay()) | ||
61 | .toPromise() | ||
62 | } | ||
63 | |||
64 | ensurePluginsAreLoaded (scope: PluginClientScope) { | ||
65 | return this.pluginsLoaded[scope].asObservable() | ||
54 | .pipe(first(), shareReplay()) | 66 | .pipe(first(), shareReplay()) |
55 | .toPromise() | 67 | .toPromise() |
56 | } | 68 | } |
@@ -90,9 +102,9 @@ export class PluginService { | |||
90 | } | 102 | } |
91 | } | 103 | } |
92 | 104 | ||
93 | async loadPluginsByScope (scope: PluginScope, isReload = false) { | 105 | async loadPluginsByScope (scope: PluginClientScope, isReload = false) { |
94 | try { | 106 | try { |
95 | await this.ensurePluginsAreLoaded() | 107 | await this.ensurePluginsAreBuilt() |
96 | 108 | ||
97 | if (!isReload) this.loadedScopes.push(scope) | 109 | if (!isReload) this.loadedScopes.push(scope) |
98 | 110 | ||
@@ -111,32 +123,24 @@ export class PluginService { | |||
111 | } | 123 | } |
112 | 124 | ||
113 | await Promise.all(promises) | 125 | await Promise.all(promises) |
126 | |||
127 | this.pluginsLoaded[scope].next(true) | ||
114 | } catch (err) { | 128 | } catch (err) { |
115 | console.error('Cannot load plugins by scope %s.', scope, err) | 129 | console.error('Cannot load plugins by scope %s.', scope, err) |
116 | } | 130 | } |
117 | } | 131 | } |
118 | 132 | ||
119 | async runHook (hookName: string, param?: any) { | 133 | async runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { |
120 | let result = param | 134 | if (!this.hooks[hookName]) return Promise.resolve(result) |
121 | |||
122 | if (!this.hooks[hookName]) return result | ||
123 | 135 | ||
124 | const wait = hookName.startsWith('static:') | 136 | const hookType = getHookType(hookName) |
125 | 137 | ||
126 | for (const hook of this.hooks[hookName]) { | 138 | for (const hook of this.hooks[hookName]) { |
127 | try { | 139 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) |
128 | const p = hook.handler(param) | 140 | |
129 | 141 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | |
130 | if (wait) { | 142 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) |
131 | result = await p | 143 | }) |
132 | } else if (p.catch) { | ||
133 | p.catch((err: Error) => { | ||
134 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err) | ||
135 | }) | ||
136 | } | ||
137 | } catch (err) { | ||
138 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err) | ||
139 | } | ||
140 | } | 144 | } |
141 | 145 | ||
142 | return result | 146 | return result |
diff --git a/client/src/app/search/search.component.ts b/client/src/app/search/search.component.ts index a7ddbe1f8..b1d732d68 100644 --- a/client/src/app/search/search.component.ts +++ b/client/src/app/search/search.component.ts | |||
@@ -10,6 +10,7 @@ import { AdvancedSearch } from '@app/search/advanced-search.model' | |||
10 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 10 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
11 | import { immutableAssign } from '@app/shared/misc/utils' | 11 | import { immutableAssign } from '@app/shared/misc/utils' |
12 | import { Video } from '@app/shared/video/video.model' | 12 | import { Video } from '@app/shared/video/video.model' |
13 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
13 | 14 | ||
14 | @Component({ | 15 | @Component({ |
15 | selector: 'my-search', | 16 | selector: 'my-search', |
@@ -41,7 +42,8 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
41 | private metaService: MetaService, | 42 | private metaService: MetaService, |
42 | private notifier: Notifier, | 43 | private notifier: Notifier, |
43 | private searchService: SearchService, | 44 | private searchService: SearchService, |
44 | private authService: AuthService | 45 | private authService: AuthService, |
46 | private hooks: HooksService | ||
45 | ) { } | 47 | ) { } |
46 | 48 | ||
47 | get user () { | 49 | get user () { |
@@ -93,18 +95,18 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
93 | 95 | ||
94 | search () { | 96 | search () { |
95 | forkJoin([ | 97 | forkJoin([ |
96 | this.searchService.searchVideos(this.currentSearch, this.pagination, this.advancedSearch), | 98 | this.getVideosObs(), |
97 | this.searchService.searchVideoChannels(this.currentSearch, immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage })) | 99 | this.getVideoChannelObs() |
98 | ]) | 100 | ]) |
99 | .subscribe( | 101 | .subscribe( |
100 | ([ videosResult, videoChannelsResult ]) => { | 102 | ([ videosResult, videoChannelsResult ]) => { |
101 | this.results = this.results | 103 | this.results = this.results |
102 | .concat(videoChannelsResult.data) | 104 | .concat(videoChannelsResult.data) |
103 | .concat(videosResult.videos) | 105 | .concat(videosResult.data) |
104 | this.pagination.totalItems = videosResult.totalVideos + videoChannelsResult.total | 106 | this.pagination.totalItems = videosResult.total + videoChannelsResult.total |
105 | 107 | ||
106 | // Focus on channels if there are no enough videos | 108 | // Focus on channels if there are no enough videos |
107 | if (this.firstSearch === true && videosResult.videos.length < this.pagination.itemsPerPage) { | 109 | if (this.firstSearch === true && videosResult.data.length < this.pagination.itemsPerPage) { |
108 | this.resetPagination() | 110 | this.resetPagination() |
109 | this.firstSearch = false | 111 | this.firstSearch = false |
110 | 112 | ||
@@ -117,7 +119,6 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
117 | 119 | ||
118 | err => this.notifier.error(err.message) | 120 | err => this.notifier.error(err.message) |
119 | ) | 121 | ) |
120 | |||
121 | } | 122 | } |
122 | 123 | ||
123 | onNearOfBottom () { | 124 | onNearOfBottom () { |
@@ -163,4 +164,35 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
163 | queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search }) | 164 | queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search }) |
164 | }) | 165 | }) |
165 | } | 166 | } |
167 | |||
168 | private getVideosObs () { | ||
169 | const params = { | ||
170 | search: this.currentSearch, | ||
171 | componentPagination: this.pagination, | ||
172 | advancedSearch: this.advancedSearch | ||
173 | } | ||
174 | |||
175 | return this.hooks.wrapObsFun( | ||
176 | this.searchService.searchVideos.bind(this.searchService), | ||
177 | params, | ||
178 | 'common', | ||
179 | 'filter:api.search.videos.list.params', | ||
180 | 'filter:api.search.videos.list.result' | ||
181 | ) | ||
182 | } | ||
183 | |||
184 | private getVideoChannelObs () { | ||
185 | const params = { | ||
186 | search: this.currentSearch, | ||
187 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }) | ||
188 | } | ||
189 | |||
190 | return this.hooks.wrapObsFun( | ||
191 | this.searchService.searchVideoChannels.bind(this.searchService), | ||
192 | params, | ||
193 | 'common', | ||
194 | 'filter:api.search.video-channels.list.params', | ||
195 | 'filter:api.search.video-channels.list.result' | ||
196 | ) | ||
197 | } | ||
166 | } | 198 | } |
diff --git a/client/src/app/search/search.service.ts b/client/src/app/search/search.service.ts index cd3bdad35..8f137a321 100644 --- a/client/src/app/search/search.service.ts +++ b/client/src/app/search/search.service.ts | |||
@@ -23,13 +23,14 @@ export class SearchService { | |||
23 | private videoService: VideoService | 23 | private videoService: VideoService |
24 | ) {} | 24 | ) {} |
25 | 25 | ||
26 | searchVideos ( | 26 | searchVideos (parameters: { |
27 | search: string, | 27 | search: string, |
28 | componentPagination: ComponentPagination, | 28 | componentPagination: ComponentPagination, |
29 | advancedSearch: AdvancedSearch | 29 | advancedSearch: AdvancedSearch |
30 | ): Observable<{ videos: Video[], totalVideos: number }> { | 30 | }): Observable<ResultList<Video>> { |
31 | const url = SearchService.BASE_SEARCH_URL + 'videos' | 31 | const { search, componentPagination, advancedSearch } = parameters |
32 | 32 | ||
33 | const url = SearchService.BASE_SEARCH_URL + 'videos' | ||
33 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) | 34 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) |
34 | 35 | ||
35 | let params = new HttpParams() | 36 | let params = new HttpParams() |
@@ -48,12 +49,13 @@ export class SearchService { | |||
48 | ) | 49 | ) |
49 | } | 50 | } |
50 | 51 | ||
51 | searchVideoChannels ( | 52 | searchVideoChannels (parameters: { |
52 | search: string, | 53 | search: string, |
53 | componentPagination: ComponentPagination | 54 | componentPagination: ComponentPagination |
54 | ): Observable<{ data: VideoChannel[], total: number }> { | 55 | }): Observable<ResultList<VideoChannel>> { |
55 | const url = SearchService.BASE_SEARCH_URL + 'video-channels' | 56 | const { search, componentPagination } = parameters |
56 | 57 | ||
58 | const url = SearchService.BASE_SEARCH_URL + 'video-channels' | ||
57 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) | 59 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) |
58 | 60 | ||
59 | let params = new HttpParams() | 61 | let params = new HttpParams() |
diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts index 98dba2d97..bd4068925 100644 --- a/client/src/app/shared/overview/overview.service.ts +++ b/client/src/app/shared/overview/overview.service.ts | |||
@@ -45,7 +45,7 @@ export class OverviewService { | |||
45 | of(object.videos) | 45 | of(object.videos) |
46 | .pipe( | 46 | .pipe( |
47 | switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })), | 47 | switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })), |
48 | map(result => result.videos), | 48 | map(result => result.data), |
49 | tap(videos => { | 49 | tap(videos => { |
50 | videosOverviewResult[key].push(immutableAssign(object, { videos })) | 50 | videosOverviewResult[key].push(immutableAssign(object, { videos })) |
51 | }) | 51 | }) |
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index cf4b5ef8e..8a247a9af 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts | |||
@@ -13,6 +13,7 @@ import { Notifier, ServerService } from '@app/core' | |||
13 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 13 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
14 | import { I18n } from '@ngx-translate/i18n-polyfill' | 14 | import { I18n } from '@ngx-translate/i18n-polyfill' |
15 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' | 15 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' |
16 | import { ResultList } from '@shared/models' | ||
16 | 17 | ||
17 | enum GroupDate { | 18 | enum GroupDate { |
18 | UNKNOWN = 0, | 19 | UNKNOWN = 0, |
@@ -73,7 +74,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor | |||
73 | private groupedDateLabels: { [id in GroupDate]: string } | 74 | private groupedDateLabels: { [id in GroupDate]: string } |
74 | private groupedDates: { [id: number]: GroupDate } = {} | 75 | private groupedDates: { [id: number]: GroupDate } = {} |
75 | 76 | ||
76 | abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }> | 77 | abstract getVideosObservable (page: number): Observable<ResultList<Video>> |
77 | 78 | ||
78 | abstract generateSyndicationList (): void | 79 | abstract generateSyndicationList (): void |
79 | 80 | ||
@@ -138,12 +139,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor | |||
138 | } | 139 | } |
139 | 140 | ||
140 | loadMoreVideos () { | 141 | loadMoreVideos () { |
141 | const observable = this.getVideosObservable(this.pagination.currentPage) | 142 | this.getVideosObservable(this.pagination.currentPage).subscribe( |
142 | 143 | ({ data, total }) => { | |
143 | observable.subscribe( | 144 | this.pagination.totalItems = total |
144 | ({ videos, totalVideos }) => { | 145 | this.videos = this.videos.concat(data) |
145 | this.pagination.totalItems = totalVideos | ||
146 | this.videos = this.videos.concat(videos) | ||
147 | 146 | ||
148 | if (this.groupByDate) this.buildGroupedDateLabels() | 147 | if (this.groupByDate) this.buildGroupedDateLabels() |
149 | 148 | ||
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index 871bc9e46..d1af13c93 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts | |||
@@ -41,7 +41,7 @@ export interface VideosProvider { | |||
41 | filter?: VideoFilter, | 41 | filter?: VideoFilter, |
42 | categoryOneOf?: number, | 42 | categoryOneOf?: number, |
43 | languageOneOf?: string[] | 43 | languageOneOf?: string[] |
44 | }): Observable<{ videos: Video[], totalVideos: number }> | 44 | }): Observable<ResultList<Video>> |
45 | } | 45 | } |
46 | 46 | ||
47 | @Injectable() | 47 | @Injectable() |
@@ -65,11 +65,11 @@ export class VideoService implements VideosProvider { | |||
65 | return VideoService.BASE_VIDEO_URL + uuid + '/watching' | 65 | return VideoService.BASE_VIDEO_URL + uuid + '/watching' |
66 | } | 66 | } |
67 | 67 | ||
68 | getVideo (uuid: string): Observable<VideoDetails> { | 68 | getVideo (options: { videoId: string }): Observable<VideoDetails> { |
69 | return this.serverService.localeObservable | 69 | return this.serverService.localeObservable |
70 | .pipe( | 70 | .pipe( |
71 | switchMap(translations => { | 71 | switchMap(translations => { |
72 | return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid) | 72 | return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + options.videoId) |
73 | .pipe(map(videoHash => ({ videoHash, translations }))) | 73 | .pipe(map(videoHash => ({ videoHash, translations }))) |
74 | }), | 74 | }), |
75 | map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)), | 75 | map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)), |
@@ -123,7 +123,7 @@ export class VideoService implements VideosProvider { | |||
123 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 123 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
124 | } | 124 | } |
125 | 125 | ||
126 | getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<{ videos: Video[], totalVideos: number }> { | 126 | getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<ResultList<Video>> { |
127 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 127 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
128 | 128 | ||
129 | let params = new HttpParams() | 129 | let params = new HttpParams() |
@@ -141,7 +141,7 @@ export class VideoService implements VideosProvider { | |||
141 | account: Account, | 141 | account: Account, |
142 | videoPagination: ComponentPagination, | 142 | videoPagination: ComponentPagination, |
143 | sort: VideoSortField | 143 | sort: VideoSortField |
144 | ): Observable<{ videos: Video[], totalVideos: number }> { | 144 | ): Observable<ResultList<Video>> { |
145 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 145 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
146 | 146 | ||
147 | let params = new HttpParams() | 147 | let params = new HttpParams() |
@@ -159,7 +159,7 @@ export class VideoService implements VideosProvider { | |||
159 | videoChannel: VideoChannel, | 159 | videoChannel: VideoChannel, |
160 | videoPagination: ComponentPagination, | 160 | videoPagination: ComponentPagination, |
161 | sort: VideoSortField | 161 | sort: VideoSortField |
162 | ): Observable<{ videos: Video[], totalVideos: number }> { | 162 | ): Observable<ResultList<Video>> { |
163 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 163 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
164 | 164 | ||
165 | let params = new HttpParams() | 165 | let params = new HttpParams() |
@@ -176,7 +176,7 @@ export class VideoService implements VideosProvider { | |||
176 | getPlaylistVideos ( | 176 | getPlaylistVideos ( |
177 | videoPlaylistId: number | string, | 177 | videoPlaylistId: number | string, |
178 | videoPagination: ComponentPagination | 178 | videoPagination: ComponentPagination |
179 | ): Observable<{ videos: Video[], totalVideos: number }> { | 179 | ): Observable<ResultList<Video>> { |
180 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 180 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
181 | 181 | ||
182 | let params = new HttpParams() | 182 | let params = new HttpParams() |
@@ -190,10 +190,11 @@ export class VideoService implements VideosProvider { | |||
190 | ) | 190 | ) |
191 | } | 191 | } |
192 | 192 | ||
193 | getUserSubscriptionVideos ( | 193 | getUserSubscriptionVideos (parameters: { |
194 | videoPagination: ComponentPagination, | 194 | videoPagination: ComponentPagination, |
195 | sort: VideoSortField | 195 | sort: VideoSortField |
196 | ): Observable<{ videos: Video[], totalVideos: number }> { | 196 | }): Observable<ResultList<Video>> { |
197 | const { videoPagination, sort } = parameters | ||
197 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 198 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
198 | 199 | ||
199 | let params = new HttpParams() | 200 | let params = new HttpParams() |
@@ -213,7 +214,7 @@ export class VideoService implements VideosProvider { | |||
213 | filter?: VideoFilter, | 214 | filter?: VideoFilter, |
214 | categoryOneOf?: number, | 215 | categoryOneOf?: number, |
215 | languageOneOf?: string[] | 216 | languageOneOf?: string[] |
216 | }): Observable<{ videos: Video[], totalVideos: number }> { | 217 | }): Observable<ResultList<Video>> { |
217 | const { videoPagination, sort, filter, categoryOneOf, languageOneOf } = parameters | 218 | const { videoPagination, sort, filter, categoryOneOf, languageOneOf } = parameters |
218 | 219 | ||
219 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | 220 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) |
@@ -344,7 +345,7 @@ export class VideoService implements VideosProvider { | |||
344 | videos.push(new Video(videoJson, translations)) | 345 | videos.push(new Video(videoJson, translations)) |
345 | } | 346 | } |
346 | 347 | ||
347 | return { videos, totalVideos } | 348 | return { total: totalVideos, data: videos } |
348 | }) | 349 | }) |
349 | ) | 350 | ) |
350 | } | 351 | } |
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts index d69f7b70e..994e0fa1e 100644 --- a/client/src/app/shared/video/videos-selection.component.ts +++ b/client/src/app/shared/video/videos-selection.component.ts | |||
@@ -21,6 +21,7 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template | |||
21 | import { VideoSortField } from '@app/shared/video/sort-field.type' | 21 | import { VideoSortField } from '@app/shared/video/sort-field.type' |
22 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | 22 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' |
23 | import { I18n } from '@ngx-translate/i18n-polyfill' | 23 | import { I18n } from '@ngx-translate/i18n-polyfill' |
24 | import { ResultList } from '@shared/models' | ||
24 | 25 | ||
25 | export type SelectionType = { [ id: number ]: boolean } | 26 | export type SelectionType = { [ id: number ]: boolean } |
26 | 27 | ||
@@ -33,7 +34,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni | |||
33 | @Input() pagination: ComponentPagination | 34 | @Input() pagination: ComponentPagination |
34 | @Input() titlePage: string | 35 | @Input() titlePage: string |
35 | @Input() miniatureDisplayOptions: MiniatureDisplayOptions | 36 | @Input() miniatureDisplayOptions: MiniatureDisplayOptions |
36 | @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<{ videos: Video[], totalVideos: number }> | 37 | @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> |
37 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective> | 38 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective> |
38 | 39 | ||
39 | @Output() selectionChange = new EventEmitter<SelectionType>() | 40 | @Output() selectionChange = new EventEmitter<SelectionType>() |
diff --git a/client/src/app/videos/+video-edit/video-update.resolver.ts b/client/src/app/videos/+video-edit/video-update.resolver.ts index 384458127..4ac517d96 100644 --- a/client/src/app/videos/+video-edit/video-update.resolver.ts +++ b/client/src/app/videos/+video-edit/video-update.resolver.ts | |||
@@ -18,7 +18,7 @@ export class VideoUpdateResolver implements Resolve<any> { | |||
18 | resolve (route: ActivatedRouteSnapshot) { | 18 | resolve (route: ActivatedRouteSnapshot) { |
19 | const uuid: string = route.params[ 'uuid' ] | 19 | const uuid: string = route.params[ 'uuid' ] |
20 | 20 | ||
21 | return this.videoService.getVideo(uuid) | 21 | return this.videoService.getVideo({ videoId: uuid }) |
22 | .pipe( | 22 | .pipe( |
23 | switchMap(video => { | 23 | switchMap(video => { |
24 | return forkJoin([ | 24 | return forkJoin([ |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.service.ts b/client/src/app/videos/+video-watch/comment/video-comment.service.ts index b8e5878c5..eb608a1a3 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.service.ts | |||
@@ -48,11 +48,13 @@ export class VideoCommentService { | |||
48 | ) | 48 | ) |
49 | } | 49 | } |
50 | 50 | ||
51 | getVideoCommentThreads ( | 51 | getVideoCommentThreads (parameters: { |
52 | videoId: number | string, | 52 | videoId: number | string, |
53 | componentPagination: ComponentPagination, | 53 | componentPagination: ComponentPagination, |
54 | sort: VideoSortField | 54 | sort: VideoSortField |
55 | ): Observable<{ comments: VideoComment[], totalComments: number}> { | 55 | }): Observable<{ comments: VideoComment[], totalComments: number}> { |
56 | const { videoId, componentPagination, sort } = parameters | ||
57 | |||
56 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) | 58 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) |
57 | 59 | ||
58 | let params = new HttpParams() | 60 | let params = new HttpParams() |
@@ -67,7 +69,11 @@ export class VideoCommentService { | |||
67 | ) | 69 | ) |
68 | } | 70 | } |
69 | 71 | ||
70 | getVideoThreadComments (videoId: number | string, threadId: number): Observable<VideoCommentThreadTree> { | 72 | getVideoThreadComments (parameters: { |
73 | videoId: number | string, | ||
74 | threadId: number | ||
75 | }): Observable<VideoCommentThreadTree> { | ||
76 | const { videoId, threadId } = parameters | ||
71 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}` | 77 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}` |
72 | 78 | ||
73 | return this.authHttp | 79 | return this.authHttp |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.ts b/client/src/app/videos/+video-watch/comment/video-comments.component.ts index 3acddbe6a..3c1a0986c 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.ts | |||
@@ -12,6 +12,7 @@ import { VideoComment } from './video-comment.model' | |||
12 | import { VideoCommentService } from './video-comment.service' | 12 | import { VideoCommentService } from './video-comment.service' |
13 | import { I18n } from '@ngx-translate/i18n-polyfill' | 13 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { Syndication } from '@app/shared/video/syndication.model' | 14 | import { Syndication } from '@app/shared/video/syndication.model' |
15 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
15 | 16 | ||
16 | @Component({ | 17 | @Component({ |
17 | selector: 'my-video-comments', | 18 | selector: 'my-video-comments', |
@@ -45,7 +46,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
45 | private confirmService: ConfirmService, | 46 | private confirmService: ConfirmService, |
46 | private videoCommentService: VideoCommentService, | 47 | private videoCommentService: VideoCommentService, |
47 | private activatedRoute: ActivatedRoute, | 48 | private activatedRoute: ActivatedRoute, |
48 | private i18n: I18n | 49 | private i18n: I18n, |
50 | private hooks: HooksService | ||
49 | ) {} | 51 | ) {} |
50 | 52 | ||
51 | ngOnInit () { | 53 | ngOnInit () { |
@@ -73,8 +75,20 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
73 | viewReplies (commentId: number, highlightThread = false) { | 75 | viewReplies (commentId: number, highlightThread = false) { |
74 | this.threadLoading[commentId] = true | 76 | this.threadLoading[commentId] = true |
75 | 77 | ||
76 | this.videoCommentService.getVideoThreadComments(this.video.id, commentId) | 78 | const params = { |
77 | .subscribe( | 79 | videoId: this.video.id, |
80 | threadId: commentId | ||
81 | } | ||
82 | |||
83 | const obs = this.hooks.wrapObsFun( | ||
84 | this.videoCommentService.getVideoThreadComments.bind(this.videoCommentService), | ||
85 | params, | ||
86 | 'video-watch', | ||
87 | 'filter:api.video-watch.video-thread-replies.list.params', | ||
88 | 'filter:api.video-watch.video-thread-replies.list.result' | ||
89 | ) | ||
90 | |||
91 | obs.subscribe( | ||
78 | res => { | 92 | res => { |
79 | this.threadComments[commentId] = res | 93 | this.threadComments[commentId] = res |
80 | this.threadLoading[commentId] = false | 94 | this.threadLoading[commentId] = false |
@@ -91,16 +105,29 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
91 | ) | 105 | ) |
92 | } | 106 | } |
93 | 107 | ||
94 | loadMoreComments () { | 108 | loadMoreThreads () { |
95 | this.videoCommentService.getVideoCommentThreads(this.video.id, this.componentPagination, this.sort) | 109 | const params = { |
96 | .subscribe( | 110 | videoId: this.video.id, |
97 | res => { | 111 | componentPagination: this.componentPagination, |
98 | this.comments = this.comments.concat(res.comments) | 112 | sort: this.sort |
99 | this.componentPagination.totalItems = res.totalComments | 113 | } |
100 | }, | ||
101 | 114 | ||
102 | err => this.notifier.error(err.message) | 115 | const obs = this.hooks.wrapObsFun( |
103 | ) | 116 | this.videoCommentService.getVideoCommentThreads.bind(this.videoCommentService), |
117 | params, | ||
118 | 'video-watch', | ||
119 | 'filter:api.video-watch.video-threads.list.params', | ||
120 | 'filter:api.video-watch.video-threads.list.result' | ||
121 | ) | ||
122 | |||
123 | obs.subscribe( | ||
124 | res => { | ||
125 | this.comments = this.comments.concat(res.comments) | ||
126 | this.componentPagination.totalItems = res.totalComments | ||
127 | }, | ||
128 | |||
129 | err => this.notifier.error(err.message) | ||
130 | ) | ||
104 | } | 131 | } |
105 | 132 | ||
106 | onCommentThreadCreated (comment: VideoComment) { | 133 | onCommentThreadCreated (comment: VideoComment) { |
@@ -169,7 +196,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
169 | this.componentPagination.currentPage++ | 196 | this.componentPagination.currentPage++ |
170 | 197 | ||
171 | if (hasMoreItems(this.componentPagination)) { | 198 | if (hasMoreItems(this.componentPagination)) { |
172 | this.loadMoreComments() | 199 | this.loadMoreThreads() |
173 | } | 200 | } |
174 | } | 201 | } |
175 | 202 | ||
@@ -197,7 +224,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
197 | 224 | ||
198 | this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) | 225 | this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) |
199 | 226 | ||
200 | this.loadMoreComments() | 227 | this.loadMoreThreads() |
201 | } | 228 | } |
202 | } | 229 | } |
203 | 230 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts index bccdaf7b2..2fb0cb0e5 100644 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts +++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts | |||
@@ -66,11 +66,11 @@ export class VideoWatchPlaylistComponent { | |||
66 | 66 | ||
67 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) { | 67 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) { |
68 | this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination) | 68 | this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination) |
69 | .subscribe(({ totalVideos, videos }) => { | 69 | .subscribe(({ total, data }) => { |
70 | this.playlistVideos = this.playlistVideos.concat(videos) | 70 | this.playlistVideos = this.playlistVideos.concat(data) |
71 | this.playlistPagination.totalItems = totalVideos | 71 | this.playlistPagination.totalItems = total |
72 | 72 | ||
73 | if (totalVideos === 0) { | 73 | if (total === 0) { |
74 | this.noPlaylistVideos = true | 74 | this.noPlaylistVideos = true |
75 | return | 75 | return |
76 | } | 76 | } |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 6d8bb4b3f..eed2ec048 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -33,6 +33,7 @@ import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils' | |||
33 | import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' | 33 | import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' |
34 | import { getStoredTheater } from '../../../assets/player/peertube-player-local-storage' | 34 | import { getStoredTheater } from '../../../assets/player/peertube-player-local-storage' |
35 | import { PluginService } from '@app/core/plugins/plugin.service' | 35 | import { PluginService } from '@app/core/plugins/plugin.service' |
36 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
36 | 37 | ||
37 | @Component({ | 38 | @Component({ |
38 | selector: 'my-video-watch', | 39 | selector: 'my-video-watch', |
@@ -93,6 +94,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
93 | private videoCaptionService: VideoCaptionService, | 94 | private videoCaptionService: VideoCaptionService, |
94 | private i18n: I18n, | 95 | private i18n: I18n, |
95 | private hotkeysService: HotkeysService, | 96 | private hotkeysService: HotkeysService, |
97 | private hooks: HooksService, | ||
96 | @Inject(LOCALE_ID) private localeId: string | 98 | @Inject(LOCALE_ID) private localeId: string |
97 | ) {} | 99 | ) {} |
98 | 100 | ||
@@ -131,7 +133,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
131 | 133 | ||
132 | this.theaterEnabled = getStoredTheater() | 134 | this.theaterEnabled = getStoredTheater() |
133 | 135 | ||
134 | this.pluginService.runHook('action:video-watch.loaded') | 136 | this.hooks.runAction('action:video-watch.init') |
135 | } | 137 | } |
136 | 138 | ||
137 | ngOnDestroy () { | 139 | ngOnDestroy () { |
@@ -246,9 +248,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
246 | 248 | ||
247 | if (this.player) this.player.pause() | 249 | if (this.player) this.player.pause() |
248 | 250 | ||
251 | const videoObs = this.hooks.wrapObsFun( | ||
252 | this.videoService.getVideo.bind(this.videoService), | ||
253 | { videoId }, | ||
254 | 'video-watch', | ||
255 | 'filter:api.video-watch.video.get.params', | ||
256 | 'filter:api.video-watch.video.get.result' | ||
257 | ) | ||
258 | |||
249 | // Video did change | 259 | // Video did change |
250 | forkJoin( | 260 | forkJoin( |
251 | this.videoService.getVideo(videoId), | 261 | videoObs, |
252 | this.videoCaptionService.listCaptions(videoId) | 262 | this.videoCaptionService.listCaptions(videoId) |
253 | ) | 263 | ) |
254 | .pipe( | 264 | .pipe( |
@@ -486,6 +496,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
486 | 496 | ||
487 | this.setOpenGraphTags() | 497 | this.setOpenGraphTags() |
488 | this.checkUserRating() | 498 | this.checkUserRating() |
499 | |||
500 | this.hooks.runAction('action:video-watch.video.loaded') | ||
489 | } | 501 | } |
490 | 502 | ||
491 | private setRating (nextRating: UserVideoRateType) { | 503 | private setRating (nextRating: UserVideoRateType) { |
diff --git a/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts b/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts index f975ff6ef..a1e65c27c 100644 --- a/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts +++ b/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts | |||
@@ -33,20 +33,24 @@ export class RecentVideosRecommendationService implements RecommendationService | |||
33 | private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> { | 33 | private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> { |
34 | const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 } | 34 | const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 } |
35 | const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' }) | 35 | const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' }) |
36 | .pipe(map(v => v.videos)) | 36 | .pipe(map(v => v.data)) |
37 | 37 | ||
38 | if (!recommendation.tags || recommendation.tags.length === 0) return defaultSubscription | 38 | if (!recommendation.tags || recommendation.tags.length === 0) return defaultSubscription |
39 | 39 | ||
40 | return this.searchService.searchVideos('', | 40 | const params = { |
41 | pagination, | 41 | search: '', |
42 | new AdvancedSearch({ tagsOneOf: recommendation.tags.join(','), sort: '-createdAt' }) | 42 | componentPagination: pagination, |
43 | ).pipe( | 43 | advancedSearch: new AdvancedSearch({ tagsOneOf: recommendation.tags.join(','), sort: '-createdAt' }) |
44 | map(v => v.videos), | 44 | } |
45 | switchMap(videos => { | 45 | |
46 | if (videos.length <= 1) return defaultSubscription | 46 | return this.searchService.searchVideos(params) |
47 | 47 | .pipe( | |
48 | return of(videos) | 48 | map(v => v.data), |
49 | }) | 49 | switchMap(videos => { |
50 | ) | 50 | if (videos.length <= 1) return defaultSubscription |
51 | |||
52 | return of(videos) | ||
53 | }) | ||
54 | ) | ||
51 | } | 55 | } |
52 | } | 56 | } |
diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts index 5de4a13af..81b6ce493 100644 --- a/client/src/app/videos/video-list/video-local.component.ts +++ b/client/src/app/videos/video-list/video-local.component.ts | |||
@@ -10,6 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
10 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
11 | import { UserRight } from '../../../../../shared/models/users' | 11 | import { UserRight } from '../../../../../shared/models/users' |
12 | import { Notifier, ServerService } from '@app/core' | 12 | import { Notifier, ServerService } from '@app/core' |
13 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
13 | 14 | ||
14 | @Component({ | 15 | @Component({ |
15 | selector: 'my-videos-local', | 16 | selector: 'my-videos-local', |
@@ -31,7 +32,8 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
31 | protected notifier: Notifier, | 32 | protected notifier: Notifier, |
32 | protected authService: AuthService, | 33 | protected authService: AuthService, |
33 | protected screenService: ScreenService, | 34 | protected screenService: ScreenService, |
34 | private videoService: VideoService | 35 | private videoService: VideoService, |
36 | private hooks: HooksService | ||
35 | ) { | 37 | ) { |
36 | super() | 38 | super() |
37 | 39 | ||
@@ -55,14 +57,21 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
55 | 57 | ||
56 | getVideosObservable (page: number) { | 58 | getVideosObservable (page: number) { |
57 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 59 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
58 | 60 | const params = { | |
59 | return this.videoService.getVideos({ | ||
60 | videoPagination: newPagination, | 61 | videoPagination: newPagination, |
61 | sort: this.sort, | 62 | sort: this.sort, |
62 | filter: this.filter, | 63 | filter: this.filter, |
63 | categoryOneOf: this.categoryOneOf, | 64 | categoryOneOf: this.categoryOneOf, |
64 | languageOneOf: this.languageOneOf | 65 | languageOneOf: this.languageOneOf |
65 | }) | 66 | } |
67 | |||
68 | return this.hooks.wrapObsFun( | ||
69 | this.videoService.getVideos.bind(this.videoService), | ||
70 | params, | ||
71 | 'common', | ||
72 | 'filter:api.videos.list.local.params', | ||
73 | 'filter:api.videos.list.local.result' | ||
74 | ) | ||
66 | } | 75 | } |
67 | 76 | ||
68 | generateSyndicationList () { | 77 | generateSyndicationList () { |
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts index 19522e6b4..638e7caed 100644 --- a/client/src/app/videos/video-list/video-recently-added.component.ts +++ b/client/src/app/videos/video-list/video-recently-added.component.ts | |||
@@ -8,6 +8,7 @@ import { VideoService } from '../../shared/video/video.service' | |||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
11 | 12 | ||
12 | @Component({ | 13 | @Component({ |
13 | selector: 'my-videos-recently-added', | 14 | selector: 'my-videos-recently-added', |
@@ -29,7 +30,8 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On | |||
29 | protected notifier: Notifier, | 30 | protected notifier: Notifier, |
30 | protected authService: AuthService, | 31 | protected authService: AuthService, |
31 | protected screenService: ScreenService, | 32 | protected screenService: ScreenService, |
32 | private videoService: VideoService | 33 | private videoService: VideoService, |
34 | private hooks: HooksService | ||
33 | ) { | 35 | ) { |
34 | super() | 36 | super() |
35 | 37 | ||
@@ -48,14 +50,20 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On | |||
48 | 50 | ||
49 | getVideosObservable (page: number) { | 51 | getVideosObservable (page: number) { |
50 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 52 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
51 | 53 | const params = { | |
52 | return this.videoService.getVideos({ | ||
53 | videoPagination: newPagination, | 54 | videoPagination: newPagination, |
54 | sort: this.sort, | 55 | sort: this.sort, |
55 | filter: undefined, | ||
56 | categoryOneOf: this.categoryOneOf, | 56 | categoryOneOf: this.categoryOneOf, |
57 | languageOneOf: this.languageOneOf | 57 | languageOneOf: this.languageOneOf |
58 | }) | 58 | } |
59 | |||
60 | return this.hooks.wrapObsFun( | ||
61 | this.videoService.getVideos.bind(this.videoService), | ||
62 | params, | ||
63 | 'common', | ||
64 | 'filter:api.videos.list.recently-added.params', | ||
65 | 'filter:api.videos.list.recently-added.result' | ||
66 | ) | ||
59 | } | 67 | } |
60 | 68 | ||
61 | generateSyndicationList () { | 69 | generateSyndicationList () { |
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts index 5f1d5055b..0e69bfd64 100644 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ b/client/src/app/videos/video-list/video-trending.component.ts | |||
@@ -8,6 +8,7 @@ import { VideoService } from '../../shared/video/video.service' | |||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
11 | 12 | ||
12 | @Component({ | 13 | @Component({ |
13 | selector: 'my-videos-trending', | 14 | selector: 'my-videos-trending', |
@@ -28,7 +29,8 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, | |||
28 | protected notifier: Notifier, | 29 | protected notifier: Notifier, |
29 | protected authService: AuthService, | 30 | protected authService: AuthService, |
30 | protected screenService: ScreenService, | 31 | protected screenService: ScreenService, |
31 | private videoService: VideoService | 32 | private videoService: VideoService, |
33 | private hooks: HooksService | ||
32 | ) { | 34 | ) { |
33 | super() | 35 | super() |
34 | } | 36 | } |
@@ -61,13 +63,20 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, | |||
61 | 63 | ||
62 | getVideosObservable (page: number) { | 64 | getVideosObservable (page: number) { |
63 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 65 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
64 | return this.videoService.getVideos({ | 66 | const params = { |
65 | videoPagination: newPagination, | 67 | videoPagination: newPagination, |
66 | sort: this.sort, | 68 | sort: this.sort, |
67 | filter: undefined, | ||
68 | categoryOneOf: this.categoryOneOf, | 69 | categoryOneOf: this.categoryOneOf, |
69 | languageOneOf: this.languageOneOf | 70 | languageOneOf: this.languageOneOf |
70 | }) | 71 | } |
72 | |||
73 | return this.hooks.wrapObsFun( | ||
74 | this.videoService.getVideos.bind(this.videoService), | ||
75 | params, | ||
76 | 'common', | ||
77 | 'filter:api.videos.list.trending.params', | ||
78 | 'filter:api.videos.list.trending.result' | ||
79 | ) | ||
71 | } | 80 | } |
72 | 81 | ||
73 | generateSyndicationList () { | 82 | generateSyndicationList () { |
diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts index 3caa371d8..ac325aeff 100644 --- a/client/src/app/videos/video-list/video-user-subscriptions.component.ts +++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts | |||
@@ -9,6 +9,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | 10 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' |
11 | import { Notifier, ServerService } from '@app/core' | 11 | import { Notifier, ServerService } from '@app/core' |
12 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
12 | 13 | ||
13 | @Component({ | 14 | @Component({ |
14 | selector: 'my-videos-user-subscriptions', | 15 | selector: 'my-videos-user-subscriptions', |
@@ -29,7 +30,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
29 | protected notifier: Notifier, | 30 | protected notifier: Notifier, |
30 | protected authService: AuthService, | 31 | protected authService: AuthService, |
31 | protected screenService: ScreenService, | 32 | protected screenService: ScreenService, |
32 | private videoService: VideoService | 33 | private videoService: VideoService, |
34 | private hooks: HooksService | ||
33 | ) { | 35 | ) { |
34 | super() | 36 | super() |
35 | 37 | ||
@@ -46,8 +48,18 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
46 | 48 | ||
47 | getVideosObservable (page: number) { | 49 | getVideosObservable (page: number) { |
48 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 50 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
51 | const params = { | ||
52 | videoPagination: newPagination, | ||
53 | sort: this.sort | ||
54 | } | ||
49 | 55 | ||
50 | return this.videoService.getUserSubscriptionVideos(newPagination, this.sort) | 56 | return this.hooks.wrapObsFun( |
57 | this.videoService.getUserSubscriptionVideos.bind(this.videoService), | ||
58 | params, | ||
59 | 'common', | ||
60 | 'filter:api.videos.list.user-subscriptions.params', | ||
61 | 'filter:api.videos.list.user-subscriptions.result' | ||
62 | ) | ||
51 | } | 63 | } |
52 | 64 | ||
53 | generateSyndicationList () { | 65 | generateSyndicationList () { |
diff --git a/client/tsconfig.json b/client/tsconfig.json index e46528d1c..ac9a3522d 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json | |||
@@ -12,6 +12,7 @@ | |||
12 | "noImplicitThis": true, | 12 | "noImplicitThis": true, |
13 | "suppressImplicitAnyIndexErrors":true, | 13 | "suppressImplicitAnyIndexErrors":true, |
14 | "alwaysStrict": true, | 14 | "alwaysStrict": true, |
15 | "strictBindCallApply": true, | ||
15 | "target": "es5", | 16 | "target": "es5", |
16 | "typeRoots": [ | 17 | "typeRoots": [ |
17 | "node_modules/@types" | 18 | "node_modules/@types" |
diff --git a/shared/core-utils/plugins/hooks.ts b/shared/core-utils/plugins/hooks.ts index 3d59a7428..5405e0529 100644 --- a/shared/core-utils/plugins/hooks.ts +++ b/shared/core-utils/plugins/hooks.ts | |||
@@ -29,7 +29,7 @@ async function internalRunHook <T> (handler: Function, hookType: HookType, resul | |||
29 | } | 29 | } |
30 | 30 | ||
31 | if (hookType === HookType.ACTION) { | 31 | if (hookType === HookType.ACTION) { |
32 | if (isCatchable(p)) p.catch(err => onError(err)) | 32 | if (isCatchable(p)) p.catch((err: any) => onError(err)) |
33 | 33 | ||
34 | return undefined | 34 | return undefined |
35 | } | 35 | } |
diff --git a/shared/models/plugins/client-hook.model.ts b/shared/models/plugins/client-hook.model.ts new file mode 100644 index 000000000..89400003e --- /dev/null +++ b/shared/models/plugins/client-hook.model.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | export type ClientFilterHookName = | ||
2 | 'filter:api.videos.list.trending.params' | | ||
3 | 'filter:api.videos.list.trending.result' | | ||
4 | |||
5 | 'filter:api.videos.list.local.params' | | ||
6 | 'filter:api.videos.list.local.result' | | ||
7 | |||
8 | 'filter:api.videos.list.recently-added.params' | | ||
9 | 'filter:api.videos.list.recently-added.result' | | ||
10 | |||
11 | 'filter:api.videos.list.user-subscriptions.params' | | ||
12 | 'filter:api.videos.list.user-subscriptions.result' | | ||
13 | |||
14 | 'filter:api.video-watch.video.get.params' | | ||
15 | 'filter:api.video-watch.video.get.result' | | ||
16 | |||
17 | 'filter:api.video-watch.video-threads.list.params' | | ||
18 | 'filter:api.video-watch.video-threads.list.result' | | ||
19 | |||
20 | 'filter:api.video-watch.video-thread-replies.list.params' | | ||
21 | 'filter:api.video-watch.video-thread-replies.list.result' | | ||
22 | |||
23 | 'filter:api.search.videos.list.params' | | ||
24 | 'filter:api.search.videos.list.result' | | ||
25 | 'filter:api.search.video-channels.list.params' | | ||
26 | 'filter:api.search.video-channels.list.result' | ||
27 | |||
28 | export type ClientActionHookName = | ||
29 | 'action:application.init' | | ||
30 | |||
31 | 'action:video-watch.init' | | ||
32 | |||
33 | 'action:video-watch.video.loaded' | ||
34 | |||
35 | export type ClientHookName = ClientActionHookName | ClientFilterHookName | ||
36 | |||
37 | export interface ClientHook { | ||
38 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> | ||
39 | } | ||
diff --git a/shared/models/plugins/plugin-client-scope.type.ts b/shared/models/plugins/plugin-client-scope.type.ts new file mode 100644 index 000000000..a2112eed7 --- /dev/null +++ b/shared/models/plugins/plugin-client-scope.type.ts | |||
@@ -0,0 +1 @@ | |||
export type PluginClientScope = 'common' | 'video-watch' | |||
diff --git a/shared/models/plugins/plugin-scope.type.ts b/shared/models/plugins/plugin-scope.type.ts deleted file mode 100644 index b63ae43ec..000000000 --- a/shared/models/plugins/plugin-scope.type.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export type PluginScope = 'common' | 'video-watch' | ||
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server-hook.model.ts index a7f88f3c4..6729e2dab 100644 --- a/shared/models/plugins/server-hook.model.ts +++ b/shared/models/plugins/server-hook.model.ts | |||
@@ -30,5 +30,5 @@ export type ServerActionHookName = | |||
30 | export type ServerHookName = ServerFilterHookName | ServerActionHookName | 30 | export type ServerHookName = ServerFilterHookName | ServerActionHookName |
31 | 31 | ||
32 | export interface ServerHook { | 32 | export interface ServerHook { |
33 | runHook (hookName: ServerHookName, params?: any) | 33 | runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> |
34 | } | 34 | } |
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index 3498f86d7..49bb01708 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | 1 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' |
2 | import { ClientScript } from '../plugins/plugin-package-json.model' | 2 | import { ClientScript } from '../plugins/plugin-package-json.model' |
3 | import { PluginClientScope } from '../plugins/plugin-scope.type' | ||
3 | 4 | ||
4 | export interface ServerConfigPlugin { | 5 | export interface ServerConfigPlugin { |
5 | name: string | 6 | name: string |
6 | version: string | 7 | version: string |
7 | description: string | 8 | description: string |
8 | clientScripts: { [name: string]: ClientScript } | 9 | clientScripts: { [name in PluginClientScope]: ClientScript } |
9 | } | 10 | } |
10 | 11 | ||
11 | export interface ServerConfigTheme extends ServerConfigPlugin { | 12 | export interface ServerConfigTheme extends ServerConfigPlugin { |