aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-12-10 15:01:12 +0100
committerChocobozzz <me@florianbigard.com>2021-12-10 15:01:12 +0100
commitd63e6d4604dfbe4938c7d66832c9202364c5bb64 (patch)
tree6bd444be722276ff214d911284a400e374bdddc8
parent03a65456f44a6152bb68975e29e076c8c5754cd6 (diff)
downloadPeerTube-d63e6d4604dfbe4938c7d66832c9202364c5bb64.tar.gz
PeerTube-d63e6d4604dfbe4938c7d66832c9202364c5bb64.tar.zst
PeerTube-d63e6d4604dfbe4938c7d66832c9202364c5bb64.zip
Add ability for plugins to register client routes
-rw-r--r--client/src/app/+plugin-pages/index.ts3
-rw-r--r--client/src/app/+plugin-pages/plugin-pages-routing.module.ts19
-rw-r--r--client/src/app/+plugin-pages/plugin-pages.component.html1
-rw-r--r--client/src/app/+plugin-pages/plugin-pages.component.ts31
-rw-r--r--client/src/app/+plugin-pages/plugin-pages.module.ts21
-rw-r--r--client/src/app/app-routing.module.ts6
-rw-r--r--client/src/app/core/plugins/plugin.service.ts31
-rw-r--r--client/src/app/core/routing/custom-reuse-strategy.ts2
-rw-r--r--client/src/root-helpers/plugins-manager.ts29
-rw-r--r--client/src/standalone/videos/embed.ts2
-rw-r--r--client/src/types/register-client-option.model.ts9
-rw-r--r--shared/models/plugins/client/index.ts1
-rw-r--r--shared/models/plugins/client/register-client-route.model.ts7
-rw-r--r--shared/models/plugins/client/register-client-settings-script.model.ts2
14 files changed, 151 insertions, 13 deletions
diff --git a/client/src/app/+plugin-pages/index.ts b/client/src/app/+plugin-pages/index.ts
new file mode 100644
index 000000000..b988f13f6
--- /dev/null
+++ b/client/src/app/+plugin-pages/index.ts
@@ -0,0 +1,3 @@
1export * from './plugin-pages-routing.module'
2export * from './plugin-pages.component'
3export * from './plugin-pages.module'
diff --git a/client/src/app/+plugin-pages/plugin-pages-routing.module.ts b/client/src/app/+plugin-pages/plugin-pages-routing.module.ts
new file mode 100644
index 000000000..b47a787e0
--- /dev/null
+++ b/client/src/app/+plugin-pages/plugin-pages-routing.module.ts
@@ -0,0 +1,19 @@
1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'
3import { PluginPagesComponent } from './plugin-pages.component'
4
5const pluginPagesRoutes: Routes = [
6 {
7 path: '**',
8 component: PluginPagesComponent,
9 data: {
10 reloadOnSameNavigation: true
11 }
12 }
13]
14
15@NgModule({
16 imports: [ RouterModule.forChild(pluginPagesRoutes) ],
17 exports: [ RouterModule ]
18})
19export class PluginPagesRoutingModule {}
diff --git a/client/src/app/+plugin-pages/plugin-pages.component.html b/client/src/app/+plugin-pages/plugin-pages.component.html
new file mode 100644
index 000000000..cf62d1bd7
--- /dev/null
+++ b/client/src/app/+plugin-pages/plugin-pages.component.html
@@ -0,0 +1 @@
<div #root></div>
diff --git a/client/src/app/+plugin-pages/plugin-pages.component.ts b/client/src/app/+plugin-pages/plugin-pages.component.ts
new file mode 100644
index 000000000..5f294ee13
--- /dev/null
+++ b/client/src/app/+plugin-pages/plugin-pages.component.ts
@@ -0,0 +1,31 @@
1import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { PluginService } from '@app/core'
4
5@Component({
6 templateUrl: './plugin-pages.component.html'
7})
8export class PluginPagesComponent implements AfterViewInit {
9 @ViewChild('root') root: ElementRef
10
11 constructor (
12 private route: ActivatedRoute,
13 private router: Router,
14 private pluginService: PluginService
15 ) {
16
17 }
18
19 ngAfterViewInit () {
20 const path = '/' + this.route.snapshot.url.map(u => u.path).join('/')
21
22 const registered = this.pluginService.getRegisteredClientRoute(path)
23 if (!registered) {
24 console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes())
25
26 return this.router.navigate([ '/404' ], { skipLocationChange: true })
27 }
28
29 registered.onMount({ rootEl: this.root.nativeElement })
30 }
31}
diff --git a/client/src/app/+plugin-pages/plugin-pages.module.ts b/client/src/app/+plugin-pages/plugin-pages.module.ts
new file mode 100644
index 000000000..86f86c752
--- /dev/null
+++ b/client/src/app/+plugin-pages/plugin-pages.module.ts
@@ -0,0 +1,21 @@
1import { NgModule } from '@angular/core'
2import { PluginPagesRoutingModule } from './plugin-pages-routing.module'
3import { PluginPagesComponent } from './plugin-pages.component'
4
5@NgModule({
6 imports: [
7 PluginPagesRoutingModule
8 ],
9
10 declarations: [
11 PluginPagesComponent
12 ],
13
14 exports: [
15 PluginPagesComponent
16 ],
17
18 providers: [
19 ]
20})
21export class PluginPagesModule { }
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index 438cb6512..42328d83d 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -58,6 +58,12 @@ const routes: Routes = [
58 }, 58 },
59 59
60 { 60 {
61 path: 'p',
62 loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule),
63 canActivateChild: [ MetaGuard ]
64 },
65
66 {
61 path: 'about', 67 path: 'about',
62 loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), 68 loadChildren: () => import('./+about/about.module').then(m => m.AboutModule),
63 canActivateChild: [ MetaGuard ] 69 canActivateChild: [ MetaGuard ]
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts
index 89391c2c5..fdbbd2d56 100644
--- a/client/src/app/core/plugins/plugin.service.ts
+++ b/client/src/app/core/plugins/plugin.service.ts
@@ -20,7 +20,8 @@ import {
20 PluginType, 20 PluginType,
21 PublicServerSetting, 21 PublicServerSetting,
22 RegisterClientFormFieldOptions, 22 RegisterClientFormFieldOptions,
23 RegisterClientSettingsScript, 23 RegisterClientSettingsScriptOptions,
24 RegisterClientRouteOptions,
24 RegisterClientVideoFieldOptions, 25 RegisterClientVideoFieldOptions,
25 ServerConfigPlugin 26 ServerConfigPlugin
26} from '@shared/models' 27} from '@shared/models'
@@ -48,7 +49,8 @@ export class PluginService implements ClientHook {
48 private formFields: FormFields = { 49 private formFields: FormFields = {
49 video: [] 50 video: []
50 } 51 }
51 private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {} 52 private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {}
53 private clientRoutes: { [ route: string ]: RegisterClientRouteOptions } = {}
52 54
53 private pluginsManager: PluginsManager 55 private pluginsManager: PluginsManager
54 56
@@ -67,7 +69,8 @@ export class PluginService implements ClientHook {
67 this.pluginsManager = new PluginsManager({ 69 this.pluginsManager = new PluginsManager({
68 peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), 70 peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this),
69 onFormFields: this.onFormFields.bind(this), 71 onFormFields: this.onFormFields.bind(this),
70 onSettingsScripts: this.onSettingsScripts.bind(this) 72 onSettingsScripts: this.onSettingsScripts.bind(this),
73 onClientRoute: this.onClientRoute.bind(this)
71 }) 74 })
72 } 75 }
73 76
@@ -123,6 +126,14 @@ export class PluginService implements ClientHook {
123 return this.settingsScripts[npmName] 126 return this.settingsScripts[npmName]
124 } 127 }
125 128
129 getRegisteredClientRoute (route: string) {
130 return this.clientRoutes[route]
131 }
132
133 getAllRegisteredClientRoutes () {
134 return Object.keys(this.clientRoutes)
135 }
136
126 translateBy (npmName: string, toTranslate: string) { 137 translateBy (npmName: string, toTranslate: string) {
127 const helpers = this.helpers[npmName] 138 const helpers = this.helpers[npmName]
128 if (!helpers) { 139 if (!helpers) {
@@ -140,12 +151,20 @@ export class PluginService implements ClientHook {
140 }) 151 })
141 } 152 }
142 153
143 private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) { 154 private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) {
144 const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) 155 const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
145 156
146 this.settingsScripts[npmName] = options 157 this.settingsScripts[npmName] = options
147 } 158 }
148 159
160 private onClientRoute (options: RegisterClientRouteOptions) {
161 const route = options.route.startsWith('/')
162 ? options.route
163 : `/${options.route}`
164
165 this.clientRoutes[route] = options
166 }
167
149 private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { 168 private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
150 const { plugin } = pluginInfo 169 const { plugin } = pluginInfo
151 const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) 170 const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
@@ -161,6 +180,10 @@ export class PluginService implements ClientHook {
161 return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` 180 return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router`
162 }, 181 },
163 182
183 getBasePluginClientPath: () => {
184 return '/p'
185 },
186
164 getSettings: () => { 187 getSettings: () => {
165 const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings' 188 const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings'
166 189
diff --git a/client/src/app/core/routing/custom-reuse-strategy.ts b/client/src/app/core/routing/custom-reuse-strategy.ts
index 1498e221f..5d3ad2e67 100644
--- a/client/src/app/core/routing/custom-reuse-strategy.ts
+++ b/client/src/app/core/routing/custom-reuse-strategy.ts
@@ -58,7 +58,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
58 58
59 // Reuse the route if we're going to and from the same route 59 // Reuse the route if we're going to and from the same route
60 shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { 60 shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
61 return future.routeConfig === curr.routeConfig 61 return future.routeConfig === curr.routeConfig && future.routeConfig?.data?.reloadOnSameNavigation !== true
62 } 62 }
63 63
64 private gb () { 64 private gb () {
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts
index 9cba63373..e574e75a3 100644
--- a/client/src/root-helpers/plugins-manager.ts
+++ b/client/src/root-helpers/plugins-manager.ts
@@ -13,7 +13,8 @@ import {
13 PluginType, 13 PluginType,
14 RegisterClientFormFieldOptions, 14 RegisterClientFormFieldOptions,
15 RegisterClientHookOptions, 15 RegisterClientHookOptions,
16 RegisterClientSettingsScript, 16 RegisterClientRouteOptions,
17 RegisterClientSettingsScriptOptions,
17 RegisterClientVideoFieldOptions, 18 RegisterClientVideoFieldOptions,
18 RegisteredExternalAuthConfig, 19 RegisteredExternalAuthConfig,
19 ServerConfigPlugin 20 ServerConfigPlugin
@@ -37,7 +38,8 @@ type PluginInfo = {
37 38
38type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers 39type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers
39type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void 40type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void
40type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) => void 41type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) => void
42type OnClientRoute = (options: RegisterClientRouteOptions) => void
41 43
42const logger = debug('peertube:plugins') 44const logger = debug('peertube:plugins')
43 45
@@ -64,15 +66,18 @@ class PluginsManager {
64 private readonly peertubeHelpersFactory: PeertubeHelpersFactory 66 private readonly peertubeHelpersFactory: PeertubeHelpersFactory
65 private readonly onFormFields: OnFormFields 67 private readonly onFormFields: OnFormFields
66 private readonly onSettingsScripts: OnSettingsScripts 68 private readonly onSettingsScripts: OnSettingsScripts
69 private readonly onClientRoute: OnClientRoute
67 70
68 constructor (options: { 71 constructor (options: {
69 peertubeHelpersFactory: PeertubeHelpersFactory 72 peertubeHelpersFactory: PeertubeHelpersFactory
70 onFormFields?: OnFormFields 73 onFormFields?: OnFormFields
71 onSettingsScripts?: OnSettingsScripts 74 onSettingsScripts?: OnSettingsScripts
75 onClientRoute?: OnClientRoute
72 }) { 76 }) {
73 this.peertubeHelpersFactory = options.peertubeHelpersFactory 77 this.peertubeHelpersFactory = options.peertubeHelpersFactory
74 this.onFormFields = options.onFormFields 78 this.onFormFields = options.onFormFields
75 this.onSettingsScripts = options.onSettingsScripts 79 this.onSettingsScripts = options.onSettingsScripts
80 this.onClientRoute = options.onClientRoute
76 } 81 }
77 82
78 static getPluginPathPrefix (isTheme: boolean) { 83 static getPluginPathPrefix (isTheme: boolean) {
@@ -221,7 +226,7 @@ class PluginsManager {
221 return this.onFormFields(commonOptions, videoFormOptions) 226 return this.onFormFields(commonOptions, videoFormOptions)
222 } 227 }
223 228
224 const registerSettingsScript = (options: RegisterClientSettingsScript) => { 229 const registerSettingsScript = (options: RegisterClientSettingsScriptOptions) => {
225 if (!this.onSettingsScripts) { 230 if (!this.onSettingsScripts) {
226 throw new Error('Registering settings script is not supported') 231 throw new Error('Registering settings script is not supported')
227 } 232 }
@@ -229,13 +234,29 @@ class PluginsManager {
229 return this.onSettingsScripts(pluginInfo, options) 234 return this.onSettingsScripts(pluginInfo, options)
230 } 235 }
231 236
237 const registerClientRoute = (options: RegisterClientRouteOptions) => {
238 if (!this.onClientRoute) {
239 throw new Error('Registering client route is not supported')
240 }
241
242 return this.onClientRoute(options)
243 }
244
232 const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) 245 const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo)
233 246
234 console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) 247 console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
235 248
236 const absURL = (environment.apiUrl || window.location.origin) + clientScript.script 249 const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
237 return dynamicImport(absURL) 250 return dynamicImport(absURL)
238 .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers })) 251 .then((script: ClientScriptModule) => {
252 return script.register({
253 registerHook,
254 registerVideoField,
255 registerSettingsScript,
256 registerClientRoute,
257 peertubeHelpers
258 })
259 })
239 .then(() => this.sortHooksByPriority()) 260 .then(() => this.sortHooksByPriority())
240 .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) 261 .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
241 } 262 }
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 9d1c6c443..874be580d 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -758,8 +758,8 @@ export class PeerTubeEmbed {
758 758
759 return { 759 return {
760 getBaseStaticRoute: unimplemented, 760 getBaseStaticRoute: unimplemented,
761
762 getBaseRouterRoute: unimplemented, 761 getBaseRouterRoute: unimplemented,
762 getBasePluginClientPath: unimplemented,
763 763
764 getSettings: unimplemented, 764 getSettings: unimplemented,
765 765
diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts
index 3415ef08f..73f82e781 100644
--- a/client/src/types/register-client-option.model.ts
+++ b/client/src/types/register-client-option.model.ts
@@ -1,7 +1,8 @@
1import { 1import {
2 RegisterClientFormFieldOptions, 2 RegisterClientFormFieldOptions,
3 RegisterClientHookOptions, 3 RegisterClientHookOptions,
4 RegisterClientSettingsScript, 4 RegisterClientRouteOptions,
5 RegisterClientSettingsScriptOptions,
5 RegisterClientVideoFieldOptions, 6 RegisterClientVideoFieldOptions,
6 ServerConfig 7 ServerConfig
7} from '@shared/models' 8} from '@shared/models'
@@ -11,7 +12,9 @@ export type RegisterClientOptions = {
11 12
12 registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void 13 registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void
13 14
14 registerSettingsScript: (options: RegisterClientSettingsScript) => void 15 registerSettingsScript: (options: RegisterClientSettingsScriptOptions) => void
16
17 registerClientRoute: (options: RegisterClientRouteOptions) => void
15 18
16 peertubeHelpers: RegisterClientHelpers 19 peertubeHelpers: RegisterClientHelpers
17} 20}
@@ -21,6 +24,8 @@ export type RegisterClientHelpers = {
21 24
22 getBaseRouterRoute: () => string 25 getBaseRouterRoute: () => string
23 26
27 getBasePluginClientPath: () => string
28
24 isLoggedIn: () => boolean 29 isLoggedIn: () => boolean
25 30
26 getAuthHeader: () => { 'Authorization': string } | undefined 31 getAuthHeader: () => { 'Authorization': string } | undefined
diff --git a/shared/models/plugins/client/index.ts b/shared/models/plugins/client/index.ts
index c500185c9..f3e3fcbcf 100644
--- a/shared/models/plugins/client/index.ts
+++ b/shared/models/plugins/client/index.ts
@@ -4,4 +4,5 @@ export * from './plugin-element-placeholder.type'
4export * from './plugin-selector-id.type' 4export * from './plugin-selector-id.type'
5export * from './register-client-form-field.model' 5export * from './register-client-form-field.model'
6export * from './register-client-hook.model' 6export * from './register-client-hook.model'
7export * from './register-client-route.model'
7export * from './register-client-settings-script.model' 8export * from './register-client-settings-script.model'
diff --git a/shared/models/plugins/client/register-client-route.model.ts b/shared/models/plugins/client/register-client-route.model.ts
new file mode 100644
index 000000000..271b67834
--- /dev/null
+++ b/shared/models/plugins/client/register-client-route.model.ts
@@ -0,0 +1,7 @@
1export interface RegisterClientRouteOptions {
2 route: string
3
4 onMount (options: {
5 rootEl: HTMLElement
6 }): void
7}
diff --git a/shared/models/plugins/client/register-client-settings-script.model.ts b/shared/models/plugins/client/register-client-settings-script.model.ts
index 481ceef96..117ca4739 100644
--- a/shared/models/plugins/client/register-client-settings-script.model.ts
+++ b/shared/models/plugins/client/register-client-settings-script.model.ts
@@ -1,6 +1,6 @@
1import { RegisterServerSettingOptions } from '../server' 1import { RegisterServerSettingOptions } from '../server'
2 2
3export interface RegisterClientSettingsScript { 3export interface RegisterClientSettingsScriptOptions {
4 isSettingHidden (options: { 4 isSettingHidden (options: {
5 setting: RegisterServerSettingOptions 5 setting: RegisterServerSettingOptions
6 formValues: { [name: string]: any } 6 formValues: { [name: string]: any }