diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-09 11:45:19 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | 7cd4d2ba10106c10602c86f74f55743ded588896 (patch) | |
tree | 81f0dd7a7ef763511158d1035f3e09e09d5dcd2c | |
parent | 8d76959e11ab7172040853fa4fadaf8d53e6aa12 (diff) | |
download | PeerTube-7cd4d2ba10106c10602c86f74f55743ded588896.tar.gz PeerTube-7cd4d2ba10106c10602c86f74f55743ded588896.tar.zst PeerTube-7cd4d2ba10106c10602c86f74f55743ded588896.zip |
WIP plugins: add theme support
34 files changed, 312 insertions, 39 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index d5b625d9c..fe9d856d0 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -85,6 +85,23 @@ | |||
85 | </ng-container> | 85 | </ng-container> |
86 | 86 | ||
87 | 87 | ||
88 | <div i18n class="inner-form-title">Theme</div> | ||
89 | |||
90 | <ng-container formGroupName="theme"> | ||
91 | <div class="form-group"> | ||
92 | <label i18n for="themeDefault">Global theme</label> | ||
93 | |||
94 | <div class="peertube-select-container"> | ||
95 | <select formControlName="default" id="themeDefault"> | ||
96 | <option i18n value="default">default</option> | ||
97 | |||
98 | <option *ngFor="let theme of availableThemes" [value]="theme">{{ theme }}</option> | ||
99 | </select> | ||
100 | </div> | ||
101 | </div> | ||
102 | </ng-container> | ||
103 | |||
104 | |||
88 | <div i18n class="inner-form-title">Signup</div> | 105 | <div i18n class="inner-form-title">Signup</div> |
89 | 106 | ||
90 | <ng-container formGroupName="signup"> | 107 | <ng-container formGroupName="signup"> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 055bae851..19a408425 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -73,6 +73,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
73 | return this.configService.videoQuotaDailyOptions | 73 | return this.configService.videoQuotaDailyOptions |
74 | } | 74 | } |
75 | 75 | ||
76 | get availableThemes () { | ||
77 | return this.serverService.getConfig().theme.registered | ||
78 | } | ||
79 | |||
76 | getResolutionKey (resolution: string) { | 80 | getResolutionKey (resolution: string) { |
77 | return 'transcoding.resolutions.' + resolution | 81 | return 'transcoding.resolutions.' + resolution |
78 | } | 82 | } |
@@ -92,6 +96,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
92 | css: null | 96 | css: null |
93 | } | 97 | } |
94 | }, | 98 | }, |
99 | theme: { | ||
100 | default: null | ||
101 | }, | ||
95 | services: { | 102 | services: { |
96 | twitter: { | 103 | twitter: { |
97 | username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, | 104 | username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-interface/index.ts b/client/src/app/+my-account/my-account-settings/my-account-interface/index.ts new file mode 100644 index 000000000..62fce79a8 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-interface/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './my-account-interface-settings.component' | |||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html new file mode 100644 index 000000000..f34e77f6a --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html | |||
@@ -0,0 +1,13 @@ | |||
1 | <form role="form" (ngSubmit)="updateInterfaceSettings()" [formGroup]="form"> | ||
2 | <div class="form-group"> | ||
3 | <label i18n for="theme">Theme</label> | ||
4 | |||
5 | <div class="peertube-select-container"> | ||
6 | <select formControlName="theme" id="theme"> | ||
7 | <option i18n value="default">default</option> | ||
8 | |||
9 | <option *ngFor="let theme of availableThemes" [value]="theme">{{ theme }}</option> | ||
10 | </select> | ||
11 | </div> | ||
12 | </div> | ||
13 | </form> | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.scss b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.scss new file mode 100644 index 000000000..629f01733 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.scss | |||
@@ -0,0 +1,16 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | input[type=submit] { | ||
5 | @include peertube-button; | ||
6 | @include orange-button; | ||
7 | |||
8 | display: block; | ||
9 | margin-top: 15px; | ||
10 | } | ||
11 | |||
12 | .peertube-select-container { | ||
13 | @include peertube-select-container(340px); | ||
14 | |||
15 | margin-bottom: 30px; | ||
16 | } | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts new file mode 100644 index 000000000..f7055072f --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts | |||
@@ -0,0 +1,64 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { Notifier, ServerService } from '@app/core' | ||
3 | import { UserUpdateMe } from '../../../../../../shared' | ||
4 | import { AuthService } from '../../../core' | ||
5 | import { FormReactive, User, UserService } from '../../../shared' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
8 | import { Subject } from 'rxjs' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-account-interface-settings', | ||
12 | templateUrl: './my-account-interface-settings.component.html', | ||
13 | styleUrls: [ './my-account-interface-settings.component.scss' ] | ||
14 | }) | ||
15 | export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit { | ||
16 | @Input() user: User = null | ||
17 | @Input() userInformationLoaded: Subject<any> | ||
18 | |||
19 | constructor ( | ||
20 | protected formValidatorService: FormValidatorService, | ||
21 | private authService: AuthService, | ||
22 | private notifier: Notifier, | ||
23 | private userService: UserService, | ||
24 | private serverService: ServerService, | ||
25 | private i18n: I18n | ||
26 | ) { | ||
27 | super() | ||
28 | } | ||
29 | |||
30 | get availableThemes () { | ||
31 | return this.serverService.getConfig().theme.registered | ||
32 | } | ||
33 | |||
34 | ngOnInit () { | ||
35 | this.buildForm({ | ||
36 | theme: null | ||
37 | }) | ||
38 | |||
39 | this.userInformationLoaded | ||
40 | .subscribe(() => { | ||
41 | this.form.patchValue({ | ||
42 | theme: this.user.theme | ||
43 | }) | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | updateInterfaceSettings () { | ||
48 | const theme = this.form.value['theme'] | ||
49 | |||
50 | const details: UserUpdateMe = { | ||
51 | theme | ||
52 | } | ||
53 | |||
54 | this.userService.updateMyProfile(details).subscribe( | ||
55 | () => { | ||
56 | this.notifier.success(this.i18n('Interface settings updated.')) | ||
57 | |||
58 | window.location.reload() | ||
59 | }, | ||
60 | |||
61 | err => this.notifier.error(err.message) | ||
62 | ) | ||
63 | } | ||
64 | } | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html index e51302f7c..eb9367d1f 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html | |||
@@ -10,9 +10,12 @@ | |||
10 | <div i18n class="account-title">Video settings</div> | 10 | <div i18n class="account-title">Video settings</div> |
11 | <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> | 11 | <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> |
12 | 12 | ||
13 | <div i18n class="account-title" id="notifications">Notifications</div> | 13 | <div i18n class="account-title">Notifications</div> |
14 | <my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences> | 14 | <my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences> |
15 | 15 | ||
16 | <div i18n class="account-title">Interface</div> | ||
17 | <my-account-interface-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-interface-settings> | ||
18 | |||
16 | <div i18n class="account-title">Password</div> | 19 | <div i18n class="account-title">Password</div> |
17 | <my-account-change-password></my-account-change-password> | 20 | <my-account-change-password></my-account-change-password> |
18 | 21 | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index f4b954e54..95fd2a3db 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { BytesPipe } from 'ngx-pipes' | 3 | import { BytesPipe } from 'ngx-pipes' |
4 | import { AuthService } from '../../core' | 4 | import { AuthService } from '../../core' |
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index a1b198e3e..5be1b0d05 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -25,19 +25,14 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b | |||
25 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' | 25 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' |
26 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' | 26 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' |
27 | import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences' | 27 | import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences' |
28 | import { | 28 | import { MyAccountVideoPlaylistCreateComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component' |
29 | MyAccountVideoPlaylistCreateComponent | 29 | import { MyAccountVideoPlaylistUpdateComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component' |
30 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component' | ||
31 | import { | ||
32 | MyAccountVideoPlaylistUpdateComponent | ||
33 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component' | ||
34 | import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component' | 30 | import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component' |
35 | import { | 31 | import { MyAccountVideoPlaylistElementsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' |
36 | MyAccountVideoPlaylistElementsComponent | ||
37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' | ||
38 | import { DragDropModule } from '@angular/cdk/drag-drop' | 32 | import { DragDropModule } from '@angular/cdk/drag-drop' |
39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' | 33 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' |
40 | import { MultiSelectModule } from 'primeng/primeng' | 34 | import { MultiSelectModule } from 'primeng/primeng' |
35 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' | ||
41 | 36 | ||
42 | @NgModule({ | 37 | @NgModule({ |
43 | imports: [ | 38 | imports: [ |
@@ -58,6 +53,7 @@ import { MultiSelectModule } from 'primeng/primeng' | |||
58 | MyAccountVideoSettingsComponent, | 53 | MyAccountVideoSettingsComponent, |
59 | MyAccountProfileComponent, | 54 | MyAccountProfileComponent, |
60 | MyAccountChangeEmailComponent, | 55 | MyAccountChangeEmailComponent, |
56 | MyAccountInterfaceSettingsComponent, | ||
61 | 57 | ||
62 | MyAccountVideosComponent, | 58 | MyAccountVideosComponent, |
63 | 59 | ||
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index 6c567d3ca..7f751f479 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -33,7 +33,7 @@ export class PluginService { | |||
33 | initializePlugins () { | 33 | initializePlugins () { |
34 | this.server.configLoaded | 34 | this.server.configLoaded |
35 | .subscribe(() => { | 35 | .subscribe(() => { |
36 | this.plugins = this.server.getConfig().plugins | 36 | this.plugins = this.server.getConfig().plugin.registered |
37 | 37 | ||
38 | this.buildScopeStruct() | 38 | this.buildScopeStruct() |
39 | 39 | ||
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index 80c52164d..7fb95fe4e 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -42,7 +42,13 @@ export class ServerService { | |||
42 | css: '' | 42 | css: '' |
43 | } | 43 | } |
44 | }, | 44 | }, |
45 | plugins: [], | 45 | plugin: { |
46 | registered: [] | ||
47 | }, | ||
48 | theme: { | ||
49 | registered: [], | ||
50 | default: 'default' | ||
51 | }, | ||
46 | email: { | 52 | email: { |
47 | enabled: false | 53 | enabled: false |
48 | }, | 54 | }, |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 95a6ce9f9..53809f82c 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -26,6 +26,8 @@ export class User implements UserServerModel { | |||
26 | videoChannels: VideoChannel[] | 26 | videoChannels: VideoChannel[] |
27 | createdAt: Date | 27 | createdAt: Date |
28 | 28 | ||
29 | theme: string | ||
30 | |||
29 | adminFlags?: UserAdminFlag | 31 | adminFlags?: UserAdminFlag |
30 | 32 | ||
31 | blocked: boolean | 33 | blocked: boolean |
@@ -49,6 +51,8 @@ export class User implements UserServerModel { | |||
49 | this.autoPlayVideo = hash.autoPlayVideo | 51 | this.autoPlayVideo = hash.autoPlayVideo |
50 | this.createdAt = hash.createdAt | 52 | this.createdAt = hash.createdAt |
51 | 53 | ||
54 | this.theme = hash.theme | ||
55 | |||
52 | this.adminFlags = hash.adminFlags | 56 | this.adminFlags = hash.adminFlags |
53 | 57 | ||
54 | this.blocked = hash.blocked | 58 | this.blocked = hash.blocked |
diff --git a/config/default.yaml b/config/default.yaml index ff3d6d54c..a1b2991cf 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -264,3 +264,6 @@ followers: | |||
264 | enabled: true | 264 | enabled: true |
265 | # Whether or not an administrator must manually validate a new follower | 265 | # Whether or not an administrator must manually validate a new follower |
266 | manual_approval: false | 266 | manual_approval: false |
267 | |||
268 | theme: | ||
269 | default: 'default' | ||
diff --git a/config/production.yaml.example b/config/production.yaml.example index 7158e076b..6c2eb4416 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -279,3 +279,6 @@ followers: | |||
279 | enabled: true | 279 | enabled: true |
280 | # Whether or not an administrator must manually validate a new follower | 280 | # Whether or not an administrator must manually validate a new follower |
281 | manual_approval: false | 281 | manual_approval: false |
282 | |||
283 | theme: | ||
284 | default: 'default' | ||
@@ -261,7 +261,7 @@ async function startApplication () { | |||
261 | updateStreamingPlaylistsInfohashesIfNeeded() | 261 | updateStreamingPlaylistsInfohashesIfNeeded() |
262 | .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err })) | 262 | .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err })) |
263 | 263 | ||
264 | await PluginManager.Instance.registerPlugins() | 264 | await PluginManager.Instance.registerPluginsAndThemes() |
265 | 265 | ||
266 | // Make server listening | 266 | // Make server listening |
267 | server.listen(port, hostname, () => { | 267 | server.listen(port, hostname, () => { |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 8563b7437..088234074 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { snakeCase } from 'lodash' | 2 | import { snakeCase } from 'lodash' |
3 | import { ServerConfig, ServerConfigPlugin, UserRight } from '../../../shared' | 3 | import { ServerConfig, UserRight } from '../../../shared' |
4 | import { About } from '../../../shared/models/server/about.model' | 4 | import { About } from '../../../shared/models/server/about.model' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
@@ -16,7 +16,7 @@ import { isNumeric } from 'validator' | |||
16 | import { objectConverter } from '../../helpers/core-utils' | 16 | import { objectConverter } from '../../helpers/core-utils' |
17 | import { CONFIG, reloadConfig } from '../../initializers/config' | 17 | import { CONFIG, reloadConfig } from '../../initializers/config' |
18 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 18 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
19 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 19 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
20 | 20 | ||
21 | const packageJSON = require('../../../../package.json') | 21 | const packageJSON = require('../../../../package.json') |
22 | const configRouter = express.Router() | 22 | const configRouter = express.Router() |
@@ -56,19 +56,23 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
56 | .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true) | 56 | .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true) |
57 | .map(r => parseInt(r, 10)) | 57 | .map(r => parseInt(r, 10)) |
58 | 58 | ||
59 | const plugins: ServerConfigPlugin[] = [] | ||
60 | const registeredPlugins = PluginManager.Instance.getRegisteredPlugins() | 59 | const registeredPlugins = PluginManager.Instance.getRegisteredPlugins() |
61 | for (const pluginName of Object.keys(registeredPlugins)) { | 60 | .map(p => ({ |
62 | const plugin = registeredPlugins[ pluginName ] | 61 | name: p.name, |
63 | if (plugin.type !== PluginType.PLUGIN) continue | 62 | version: p.version, |
64 | 63 | description: p.description, | |
65 | plugins.push({ | 64 | clientScripts: p.clientScripts |
66 | name: plugin.name, | 65 | })) |
67 | version: plugin.version, | 66 | |
68 | description: plugin.description, | 67 | const registeredThemes = PluginManager.Instance.getRegisteredThemes() |
69 | clientScripts: plugin.clientScripts | 68 | .map(t => ({ |
70 | }) | 69 | name: t.name, |
71 | } | 70 | version: t.version, |
71 | description: t.description, | ||
72 | clientScripts: t.clientScripts | ||
73 | })) | ||
74 | |||
75 | const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT) | ||
72 | 76 | ||
73 | const json: ServerConfig = { | 77 | const json: ServerConfig = { |
74 | instance: { | 78 | instance: { |
@@ -82,7 +86,13 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
82 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS | 86 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS |
83 | } | 87 | } |
84 | }, | 88 | }, |
85 | plugins, | 89 | plugin: { |
90 | registered: registeredPlugins | ||
91 | }, | ||
92 | theme: { | ||
93 | registered: registeredThemes, | ||
94 | default: defaultTheme | ||
95 | }, | ||
86 | email: { | 96 | email: { |
87 | enabled: Emailer.isEnabled() | 97 | enabled: Emailer.isEnabled() |
88 | }, | 98 | }, |
@@ -240,6 +250,9 @@ function customConfig (): CustomConfig { | |||
240 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT | 250 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT |
241 | } | 251 | } |
242 | }, | 252 | }, |
253 | theme: { | ||
254 | default: CONFIG.THEME.DEFAULT | ||
255 | }, | ||
243 | services: { | 256 | services: { |
244 | twitter: { | 257 | twitter: { |
245 | username: CONFIG.SERVICES.TWITTER.USERNAME, | 258 | username: CONFIG.SERVICES.TWITTER.USERNAME, |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index a078334fe..e7ed3de64 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -183,6 +183,7 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
183 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo | 183 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo |
184 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 184 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled |
185 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 185 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages |
186 | if (body.theme !== undefined) user.theme = body.theme | ||
186 | 187 | ||
187 | if (body.email !== undefined) { | 188 | if (body.email !== undefined) { |
188 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 189 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts index 2fcdc581f..4ab5f9ce8 100644 --- a/server/helpers/custom-validators/plugins.ts +++ b/server/helpers/custom-validators/plugins.ts | |||
@@ -4,6 +4,7 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type' | |||
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
5 | import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' | 5 | import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' |
6 | import { isUrlValid } from './activitypub/misc' | 6 | import { isUrlValid } from './activitypub/misc' |
7 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | ||
7 | 8 | ||
8 | const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS | 9 | const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS |
9 | 10 | ||
@@ -61,6 +62,10 @@ function isCSSPathsValid (css: any[]) { | |||
61 | return isArray(css) && css.every(c => isSafePath(c)) | 62 | return isArray(css) && css.every(c => isSafePath(c)) |
62 | } | 63 | } |
63 | 64 | ||
65 | function isThemeValid (name: string) { | ||
66 | return isPluginNameValid(name) && isThemeRegistered(name) | ||
67 | } | ||
68 | |||
64 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { | 69 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { |
65 | return isNpmPluginNameValid(packageJSON.name) && | 70 | return isNpmPluginNameValid(packageJSON.name) && |
66 | isPluginDescriptionValid(packageJSON.description) && | 71 | isPluginDescriptionValid(packageJSON.description) && |
@@ -82,6 +87,7 @@ function isLibraryCodeValid (library: any) { | |||
82 | export { | 87 | export { |
83 | isPluginTypeValid, | 88 | isPluginTypeValid, |
84 | isPackageJSONValid, | 89 | isPackageJSONValid, |
90 | isThemeValid, | ||
85 | isPluginVersionValid, | 91 | isPluginVersionValid, |
86 | isPluginNameValid, | 92 | isPluginNameValid, |
87 | isPluginDescriptionValid, | 93 | isPluginDescriptionValid, |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 1f5ec20df..c94bca2f8 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -29,7 +29,8 @@ function checkMissedConfig () { | |||
29 | 'followers.instance.enabled', 'followers.instance.manual_approval', | 29 | 'followers.instance.enabled', 'followers.instance.manual_approval', |
30 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', | 30 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', |
31 | 'history.videos.max_age', 'views.videos.remote.max_age', | 31 | 'history.videos.max_age', 'views.videos.remote.max_age', |
32 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max' | 32 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', |
33 | 'theme.default' | ||
33 | ] | 34 | ] |
34 | const requiredAlternatives = [ | 35 | const requiredAlternatives = [ |
35 | [ // set | 36 | [ // set |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 6737edcd6..dfc4bea21 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -224,6 +224,9 @@ const CONFIG = { | |||
224 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') }, | 224 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') }, |
225 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } | 225 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } |
226 | } | 226 | } |
227 | }, | ||
228 | THEME: { | ||
229 | get DEFAULT () { return config.get<string>('theme.default') } | ||
227 | } | 230 | } |
228 | } | 231 | } |
229 | 232 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 8ceefbd0e..9d61ed537 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 395 | 17 | const LAST_MIGRATION_VERSION = 400 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
@@ -585,6 +585,8 @@ const P2P_MEDIA_LOADER_PEER_VERSION = 2 | |||
585 | const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css' | 585 | const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css' |
586 | const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME) | 586 | const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME) |
587 | 587 | ||
588 | const DEFAULT_THEME = 'default' | ||
589 | |||
588 | // --------------------------------------------------------------------------- | 590 | // --------------------------------------------------------------------------- |
589 | 591 | ||
590 | // Special constants for a test instance | 592 | // Special constants for a test instance |
@@ -667,6 +669,7 @@ export { | |||
667 | HLS_STREAMING_PLAYLIST_DIRECTORY, | 669 | HLS_STREAMING_PLAYLIST_DIRECTORY, |
668 | FEEDS, | 670 | FEEDS, |
669 | JOB_TTL, | 671 | JOB_TTL, |
672 | DEFAULT_THEME, | ||
670 | NSFW_POLICY_TYPES, | 673 | NSFW_POLICY_TYPES, |
671 | STATIC_MAX_AGE, | 674 | STATIC_MAX_AGE, |
672 | STATIC_PATHS, | 675 | STATIC_PATHS, |
diff --git a/server/initializers/migrations/0400-user-theme.ts b/server/initializers/migrations/0400-user-theme.ts new file mode 100644 index 000000000..2c1763890 --- /dev/null +++ b/server/initializers/migrations/0400-user-theme.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.STRING, | ||
11 | allowNull: false, | ||
12 | defaultValue: 'default' | ||
13 | } | ||
14 | |||
15 | await utils.queryInterface.addColumn('user', 'theme', data) | ||
16 | } | ||
17 | |||
18 | function down (options) { | ||
19 | throw new Error('Not implemented.') | ||
20 | } | ||
21 | |||
22 | export { | ||
23 | up, | ||
24 | down | ||
25 | } | ||
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 7cbfa8569..8496979f8 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -11,6 +11,7 @@ import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' | |||
11 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 11 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
12 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 12 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
13 | import { outputFile } from 'fs-extra' | 13 | import { outputFile } from 'fs-extra' |
14 | import { ServerConfigPlugin } from '../../../shared/models/server' | ||
14 | 15 | ||
15 | export interface RegisteredPlugin { | 16 | export interface RegisteredPlugin { |
16 | name: string | 17 | name: string |
@@ -47,7 +48,7 @@ export class PluginManager { | |||
47 | private constructor () { | 48 | private constructor () { |
48 | } | 49 | } |
49 | 50 | ||
50 | async registerPlugins () { | 51 | async registerPluginsAndThemes () { |
51 | await this.resetCSSGlobalFile() | 52 | await this.resetCSSGlobalFile() |
52 | 53 | ||
53 | const plugins = await PluginModel.listEnabledPluginsAndThemes() | 54 | const plugins = await PluginModel.listEnabledPluginsAndThemes() |
@@ -63,12 +64,20 @@ export class PluginManager { | |||
63 | this.sortHooksByPriority() | 64 | this.sortHooksByPriority() |
64 | } | 65 | } |
65 | 66 | ||
67 | getRegisteredPluginOrTheme (name: string) { | ||
68 | return this.registeredPlugins[name] | ||
69 | } | ||
70 | |||
66 | getRegisteredPlugin (name: string) { | 71 | getRegisteredPlugin (name: string) { |
67 | return this.registeredPlugins[ name ] | 72 | const registered = this.getRegisteredPluginOrTheme(name) |
73 | |||
74 | if (!registered || registered.type !== PluginType.PLUGIN) return undefined | ||
75 | |||
76 | return registered | ||
68 | } | 77 | } |
69 | 78 | ||
70 | getRegisteredTheme (name: string) { | 79 | getRegisteredTheme (name: string) { |
71 | const registered = this.getRegisteredPlugin(name) | 80 | const registered = this.getRegisteredPluginOrTheme(name) |
72 | 81 | ||
73 | if (!registered || registered.type !== PluginType.THEME) return undefined | 82 | if (!registered || registered.type !== PluginType.THEME) return undefined |
74 | 83 | ||
@@ -76,7 +85,11 @@ export class PluginManager { | |||
76 | } | 85 | } |
77 | 86 | ||
78 | getRegisteredPlugins () { | 87 | getRegisteredPlugins () { |
79 | return this.registeredPlugins | 88 | return this.getRegisteredPluginsOrThemes(PluginType.PLUGIN) |
89 | } | ||
90 | |||
91 | getRegisteredThemes () { | ||
92 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) | ||
80 | } | 93 | } |
81 | 94 | ||
82 | async runHook (hookName: string, param?: any) { | 95 | async runHook (hookName: string, param?: any) { |
@@ -309,6 +322,19 @@ export class PluginManager { | |||
309 | } | 322 | } |
310 | } | 323 | } |
311 | 324 | ||
325 | private getRegisteredPluginsOrThemes (type: PluginType) { | ||
326 | const plugins: RegisteredPlugin[] = [] | ||
327 | |||
328 | for (const pluginName of Object.keys(this.registeredPlugins)) { | ||
329 | const plugin = this.registeredPlugins[ pluginName ] | ||
330 | if (plugin.type !== type) continue | ||
331 | |||
332 | plugins.push(plugin) | ||
333 | } | ||
334 | |||
335 | return plugins | ||
336 | } | ||
337 | |||
312 | static get Instance () { | 338 | static get Instance () { |
313 | return this.instance || (this.instance = new this()) | 339 | return this.instance || (this.instance = new this()) |
314 | } | 340 | } |
diff --git a/server/lib/plugins/theme-utils.ts b/server/lib/plugins/theme-utils.ts new file mode 100644 index 000000000..066339e65 --- /dev/null +++ b/server/lib/plugins/theme-utils.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { DEFAULT_THEME } from '../../initializers/constants' | ||
2 | import { PluginManager } from './plugin-manager' | ||
3 | import { CONFIG } from '../../initializers/config' | ||
4 | |||
5 | function getThemeOrDefault (name: string) { | ||
6 | if (isThemeRegistered(name)) return name | ||
7 | |||
8 | // Fallback to admin default theme | ||
9 | if (name !== CONFIG.THEME.DEFAULT) return getThemeOrDefault(CONFIG.THEME.DEFAULT) | ||
10 | |||
11 | return DEFAULT_THEME | ||
12 | } | ||
13 | |||
14 | function isThemeRegistered (name: string) { | ||
15 | if (name === DEFAULT_THEME) return true | ||
16 | |||
17 | return !!PluginManager.Instance.getRegisteredThemes() | ||
18 | .find(r => r.name === name) | ||
19 | } | ||
20 | |||
21 | export { | ||
22 | getThemeOrDefault, | ||
23 | isThemeRegistered | ||
24 | } | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index d015fa6fe..31b131914 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isUserNSFWPolicyValid, isUserVideoQuotaValid, isUserVideoQuotaDailyValid } from '../../helpers/custom-validators/users' | 3 | import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { Emailer } from '../../lib/emailer' | 6 | import { Emailer } from '../../lib/emailer' |
7 | import { areValidationErrors } from './utils' | 7 | import { areValidationErrors } from './utils' |
8 | import { isThemeValid } from '../../helpers/custom-validators/plugins' | ||
8 | 9 | ||
9 | const customConfigUpdateValidator = [ | 10 | const customConfigUpdateValidator = [ |
10 | body('instance.name').exists().withMessage('Should have a valid instance name'), | 11 | body('instance.name').exists().withMessage('Should have a valid instance name'), |
@@ -47,6 +48,8 @@ const customConfigUpdateValidator = [ | |||
47 | body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), | 48 | body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), |
48 | body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), | 49 | body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), |
49 | 50 | ||
51 | body('theme.default').custom(isThemeValid).withMessage('Should have a valid theme'), | ||
52 | |||
50 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 53 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
51 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | 54 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) |
52 | 55 | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 672299ee1..fcb461624 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -16,7 +16,7 @@ const servePluginStaticDirectoryValidator = [ | |||
16 | 16 | ||
17 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
18 | 18 | ||
19 | const plugin = PluginManager.Instance.getRegisteredPlugin(req.params.pluginName) | 19 | const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(req.params.pluginName) |
20 | 20 | ||
21 | if (!plugin || plugin.version !== req.params.pluginVersion) { | 21 | if (!plugin || plugin.version !== req.params.pluginVersion) { |
22 | return res.sendStatus(404) | 22 | return res.sendStatus(404) |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 947ed36c3..df7f77b84 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -28,6 +28,7 @@ import { ActorModel } from '../../models/activitypub/actor' | |||
28 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' | 28 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' |
29 | import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' | 29 | import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
30 | import { UserRegister } from '../../../shared/models/users/user-register.model' | 30 | import { UserRegister } from '../../../shared/models/users/user-register.model' |
31 | import { isThemeValid } from '../../helpers/custom-validators/plugins' | ||
31 | 32 | ||
32 | const usersAddValidator = [ | 33 | const usersAddValidator = [ |
33 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 34 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
@@ -204,6 +205,9 @@ const usersUpdateMeValidator = [ | |||
204 | body('videosHistoryEnabled') | 205 | body('videosHistoryEnabled') |
205 | .optional() | 206 | .optional() |
206 | .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'), | 207 | .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'), |
208 | body('theme') | ||
209 | .optional() | ||
210 | .custom(isThemeValid).withMessage('Should have a valid theme'), | ||
207 | 211 | ||
208 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 212 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
209 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 213 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 0f425bb82..b8ca1dd5c 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -44,7 +44,7 @@ import { VideoChannelModel } from '../video/video-channel' | |||
44 | import { AccountModel } from './account' | 44 | import { AccountModel } from './account' |
45 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | 45 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' |
46 | import { values } from 'lodash' | 46 | import { values } from 'lodash' |
47 | import { NSFW_POLICY_TYPES } from '../../initializers/constants' | 47 | import { DEFAULT_THEME, NSFW_POLICY_TYPES } from '../../initializers/constants' |
48 | import { clearCacheByUserId } from '../../lib/oauth-model' | 48 | import { clearCacheByUserId } from '../../lib/oauth-model' |
49 | import { UserNotificationSettingModel } from './user-notification-setting' | 49 | import { UserNotificationSettingModel } from './user-notification-setting' |
50 | import { VideoModel } from '../video/video' | 50 | import { VideoModel } from '../video/video' |
@@ -52,6 +52,8 @@ import { ActorModel } from '../activitypub/actor' | |||
52 | import { ActorFollowModel } from '../activitypub/actor-follow' | 52 | import { ActorFollowModel } from '../activitypub/actor-follow' |
53 | import { VideoImportModel } from '../video/video-import' | 53 | import { VideoImportModel } from '../video/video-import' |
54 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' | 54 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' |
55 | import { isThemeValid } from '../../helpers/custom-validators/plugins' | ||
56 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | ||
55 | 57 | ||
56 | enum ScopeNames { | 58 | enum ScopeNames { |
57 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 59 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
@@ -187,6 +189,12 @@ export class UserModel extends Model<UserModel> { | |||
187 | @Column(DataType.BIGINT) | 189 | @Column(DataType.BIGINT) |
188 | videoQuotaDaily: number | 190 | videoQuotaDaily: number |
189 | 191 | ||
192 | @AllowNull(false) | ||
193 | @Default(DEFAULT_THEME) | ||
194 | @Is('UserTheme', value => throwIfNotValid(value, isThemeValid, 'theme')) | ||
195 | @Column | ||
196 | theme: string | ||
197 | |||
190 | @CreatedAt | 198 | @CreatedAt |
191 | createdAt: Date | 199 | createdAt: Date |
192 | 200 | ||
@@ -560,6 +568,7 @@ export class UserModel extends Model<UserModel> { | |||
560 | autoPlayVideo: this.autoPlayVideo, | 568 | autoPlayVideo: this.autoPlayVideo, |
561 | videoLanguages: this.videoLanguages, | 569 | videoLanguages: this.videoLanguages, |
562 | role: this.role, | 570 | role: this.role, |
571 | theme: getThemeOrDefault(this.theme), | ||
563 | roleLabel: USER_ROLE_LABELS[ this.role ], | 572 | roleLabel: USER_ROLE_LABELS[ this.role ], |
564 | videoQuota: this.videoQuota, | 573 | videoQuota: this.videoQuota, |
565 | videoQuotaDaily: this.videoQuotaDaily, | 574 | videoQuotaDaily: this.videoQuotaDaily, |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index a0d9392dc..7773ae1e7 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -27,6 +27,9 @@ describe('Test config API validators', function () { | |||
27 | css: 'body { background-color: red; }' | 27 | css: 'body { background-color: red; }' |
28 | } | 28 | } |
29 | }, | 29 | }, |
30 | theme: { | ||
31 | default: 'default' | ||
32 | }, | ||
30 | services: { | 33 | services: { |
31 | twitter: { | 34 | twitter: { |
32 | username: '@MySuperUsername', | 35 | username: '@MySuperUsername', |
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index c39516dee..78fdc9cc0 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -190,6 +190,9 @@ describe('Test config', function () { | |||
190 | css: 'body { background-color: red; }' | 190 | css: 'body { background-color: red; }' |
191 | } | 191 | } |
192 | }, | 192 | }, |
193 | theme: { | ||
194 | default: 'default' | ||
195 | }, | ||
193 | services: { | 196 | services: { |
194 | twitter: { | 197 | twitter: { |
195 | username: '@Kuja', | 198 | username: '@Kuja', |
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index 2b7965bc2..8736f083f 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts | |||
@@ -59,6 +59,9 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) { | |||
59 | css: 'body { background-color: red; }' | 59 | css: 'body { background-color: red; }' |
60 | } | 60 | } |
61 | }, | 61 | }, |
62 | theme: { | ||
63 | default: 'default' | ||
64 | }, | ||
62 | services: { | 65 | services: { |
63 | twitter: { | 66 | twitter: { |
64 | username: '@MySuperUsername', | 67 | username: '@MySuperUsername', |
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index 670553d16..a0541f5b6 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -15,6 +15,10 @@ export interface CustomConfig { | |||
15 | } | 15 | } |
16 | } | 16 | } |
17 | 17 | ||
18 | theme: { | ||
19 | default: string | ||
20 | } | ||
21 | |||
18 | services: { | 22 | services: { |
19 | twitter: { | 23 | twitter: { |
20 | username: string | 24 | username: string |
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index c259a849a..d6c660aac 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts | |||
@@ -24,7 +24,14 @@ export interface ServerConfig { | |||
24 | } | 24 | } |
25 | } | 25 | } |
26 | 26 | ||
27 | plugins: ServerConfigPlugin[] | 27 | plugin: { |
28 | registered: ServerConfigPlugin[] | ||
29 | } | ||
30 | |||
31 | theme: { | ||
32 | registered: ServerConfigPlugin[] | ||
33 | default: string | ||
34 | } | ||
28 | 35 | ||
29 | email: { | 36 | email: { |
30 | enabled: boolean | 37 | enabled: boolean |
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index 6e6cd7115..b6c0002e5 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts | |||
@@ -13,4 +13,6 @@ export interface UserUpdateMe { | |||
13 | email?: string | 13 | email?: string |
14 | currentPassword?: string | 14 | currentPassword?: string |
15 | password?: string | 15 | password?: string |
16 | |||
17 | theme?: string | ||
16 | } | 18 | } |