WIP plugins: add theme support
This commit is contained in:
parent
8d76959e11
commit
7cd4d2ba10
|
@ -85,6 +85,23 @@
|
|||
</ng-container>
|
||||
|
||||
|
||||
<div i18n class="inner-form-title">Theme</div>
|
||||
|
||||
<ng-container formGroupName="theme">
|
||||
<div class="form-group">
|
||||
<label i18n for="themeDefault">Global theme</label>
|
||||
|
||||
<div class="peertube-select-container">
|
||||
<select formControlName="default" id="themeDefault">
|
||||
<option i18n value="default">default</option>
|
||||
|
||||
<option *ngFor="let theme of availableThemes" [value]="theme">{{ theme }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<div i18n class="inner-form-title">Signup</div>
|
||||
|
||||
<ng-container formGroupName="signup">
|
||||
|
|
|
@ -73,6 +73,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
return this.configService.videoQuotaDailyOptions
|
||||
}
|
||||
|
||||
get availableThemes () {
|
||||
return this.serverService.getConfig().theme.registered
|
||||
}
|
||||
|
||||
getResolutionKey (resolution: string) {
|
||||
return 'transcoding.resolutions.' + resolution
|
||||
}
|
||||
|
@ -92,6 +96,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
css: null
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
default: null
|
||||
},
|
||||
services: {
|
||||
twitter: {
|
||||
username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './my-account-interface-settings.component'
|
|
@ -0,0 +1,13 @@
|
|||
<form role="form" (ngSubmit)="updateInterfaceSettings()" [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<label i18n for="theme">Theme</label>
|
||||
|
||||
<div class="peertube-select-container">
|
||||
<select formControlName="theme" id="theme">
|
||||
<option i18n value="default">default</option>
|
||||
|
||||
<option *ngFor="let theme of availableThemes" [value]="theme">{{ theme }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,16 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
input[type=submit] {
|
||||
@include peertube-button;
|
||||
@include orange-button;
|
||||
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.peertube-select-container {
|
||||
@include peertube-select-container(340px);
|
||||
|
||||
margin-bottom: 30px;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { UserUpdateMe } from '../../../../../../shared'
|
||||
import { AuthService } from '../../../core'
|
||||
import { FormReactive, User, UserService } from '../../../shared'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
||||
import { Subject } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-interface-settings',
|
||||
templateUrl: './my-account-interface-settings.component.html',
|
||||
styleUrls: [ './my-account-interface-settings.component.scss' ]
|
||||
})
|
||||
export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit {
|
||||
@Input() user: User = null
|
||||
@Input() userInformationLoaded: Subject<any>
|
||||
|
||||
constructor (
|
||||
protected formValidatorService: FormValidatorService,
|
||||
private authService: AuthService,
|
||||
private notifier: Notifier,
|
||||
private userService: UserService,
|
||||
private serverService: ServerService,
|
||||
private i18n: I18n
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
get availableThemes () {
|
||||
return this.serverService.getConfig().theme.registered
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.buildForm({
|
||||
theme: null
|
||||
})
|
||||
|
||||
this.userInformationLoaded
|
||||
.subscribe(() => {
|
||||
this.form.patchValue({
|
||||
theme: this.user.theme
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updateInterfaceSettings () {
|
||||
const theme = this.form.value['theme']
|
||||
|
||||
const details: UserUpdateMe = {
|
||||
theme
|
||||
}
|
||||
|
||||
this.userService.updateMyProfile(details).subscribe(
|
||||
() => {
|
||||
this.notifier.success(this.i18n('Interface settings updated.'))
|
||||
|
||||
window.location.reload()
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -10,9 +10,12 @@
|
|||
<div i18n class="account-title">Video settings</div>
|
||||
<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
|
||||
|
||||
<div i18n class="account-title" id="notifications">Notifications</div>
|
||||
<div i18n class="account-title">Notifications</div>
|
||||
<my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences>
|
||||
|
||||
<div i18n class="account-title">Interface</div>
|
||||
<my-account-interface-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-interface-settings>
|
||||
|
||||
<div i18n class="account-title">Password</div>
|
||||
<my-account-change-password></my-account-change-password>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit, ViewChild } from '@angular/core'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { Notifier } from '@app/core'
|
||||
import { BytesPipe } from 'ngx-pipes'
|
||||
import { AuthService } from '../../core'
|
||||
|
|
|
@ -25,19 +25,14 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b
|
|||
import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component'
|
||||
import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component'
|
||||
import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences'
|
||||
import {
|
||||
MyAccountVideoPlaylistCreateComponent
|
||||
} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component'
|
||||
import {
|
||||
MyAccountVideoPlaylistUpdateComponent
|
||||
} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component'
|
||||
import { MyAccountVideoPlaylistCreateComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component'
|
||||
import { MyAccountVideoPlaylistUpdateComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component'
|
||||
import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component'
|
||||
import {
|
||||
MyAccountVideoPlaylistElementsComponent
|
||||
} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
|
||||
import { MyAccountVideoPlaylistElementsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email'
|
||||
import { MultiSelectModule } from 'primeng/primeng'
|
||||
import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -58,6 +53,7 @@ import { MultiSelectModule } from 'primeng/primeng'
|
|||
MyAccountVideoSettingsComponent,
|
||||
MyAccountProfileComponent,
|
||||
MyAccountChangeEmailComponent,
|
||||
MyAccountInterfaceSettingsComponent,
|
||||
|
||||
MyAccountVideosComponent,
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ export class PluginService {
|
|||
initializePlugins () {
|
||||
this.server.configLoaded
|
||||
.subscribe(() => {
|
||||
this.plugins = this.server.getConfig().plugins
|
||||
this.plugins = this.server.getConfig().plugin.registered
|
||||
|
||||
this.buildScopeStruct()
|
||||
|
||||
|
|
|
@ -42,7 +42,13 @@ export class ServerService {
|
|||
css: ''
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
plugin: {
|
||||
registered: []
|
||||
},
|
||||
theme: {
|
||||
registered: [],
|
||||
default: 'default'
|
||||
},
|
||||
email: {
|
||||
enabled: false
|
||||
},
|
||||
|
|
|
@ -26,6 +26,8 @@ export class User implements UserServerModel {
|
|||
videoChannels: VideoChannel[]
|
||||
createdAt: Date
|
||||
|
||||
theme: string
|
||||
|
||||
adminFlags?: UserAdminFlag
|
||||
|
||||
blocked: boolean
|
||||
|
@ -49,6 +51,8 @@ export class User implements UserServerModel {
|
|||
this.autoPlayVideo = hash.autoPlayVideo
|
||||
this.createdAt = hash.createdAt
|
||||
|
||||
this.theme = hash.theme
|
||||
|
||||
this.adminFlags = hash.adminFlags
|
||||
|
||||
this.blocked = hash.blocked
|
||||
|
|
|
@ -264,3 +264,6 @@ followers:
|
|||
enabled: true
|
||||
# Whether or not an administrator must manually validate a new follower
|
||||
manual_approval: false
|
||||
|
||||
theme:
|
||||
default: 'default'
|
||||
|
|
|
@ -279,3 +279,6 @@ followers:
|
|||
enabled: true
|
||||
# Whether or not an administrator must manually validate a new follower
|
||||
manual_approval: false
|
||||
|
||||
theme:
|
||||
default: 'default'
|
||||
|
|
|
@ -261,7 +261,7 @@ async function startApplication () {
|
|||
updateStreamingPlaylistsInfohashesIfNeeded()
|
||||
.catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
|
||||
|
||||
await PluginManager.Instance.registerPlugins()
|
||||
await PluginManager.Instance.registerPluginsAndThemes()
|
||||
|
||||
// Make server listening
|
||||
server.listen(port, hostname, () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as express from 'express'
|
||||
import { snakeCase } from 'lodash'
|
||||
import { ServerConfig, ServerConfigPlugin, UserRight } from '../../../shared'
|
||||
import { ServerConfig, UserRight } from '../../../shared'
|
||||
import { About } from '../../../shared/models/server/about.model'
|
||||
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
||||
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
|
||||
|
@ -16,7 +16,7 @@ import { isNumeric } from 'validator'
|
|||
import { objectConverter } from '../../helpers/core-utils'
|
||||
import { CONFIG, reloadConfig } from '../../initializers/config'
|
||||
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
||||
import { PluginType } from '../../../shared/models/plugins/plugin.type'
|
||||
import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
|
||||
|
||||
const packageJSON = require('../../../../package.json')
|
||||
const configRouter = express.Router()
|
||||
|
@ -56,19 +56,23 @@ async function getConfig (req: express.Request, res: express.Response) {
|
|||
.filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
|
||||
.map(r => parseInt(r, 10))
|
||||
|
||||
const plugins: ServerConfigPlugin[] = []
|
||||
const registeredPlugins = PluginManager.Instance.getRegisteredPlugins()
|
||||
for (const pluginName of Object.keys(registeredPlugins)) {
|
||||
const plugin = registeredPlugins[ pluginName ]
|
||||
if (plugin.type !== PluginType.PLUGIN) continue
|
||||
.map(p => ({
|
||||
name: p.name,
|
||||
version: p.version,
|
||||
description: p.description,
|
||||
clientScripts: p.clientScripts
|
||||
}))
|
||||
|
||||
plugins.push({
|
||||
name: plugin.name,
|
||||
version: plugin.version,
|
||||
description: plugin.description,
|
||||
clientScripts: plugin.clientScripts
|
||||
})
|
||||
}
|
||||
const registeredThemes = PluginManager.Instance.getRegisteredThemes()
|
||||
.map(t => ({
|
||||
name: t.name,
|
||||
version: t.version,
|
||||
description: t.description,
|
||||
clientScripts: t.clientScripts
|
||||
}))
|
||||
|
||||
const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT)
|
||||
|
||||
const json: ServerConfig = {
|
||||
instance: {
|
||||
|
@ -82,7 +86,13 @@ async function getConfig (req: express.Request, res: express.Response) {
|
|||
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
|
||||
}
|
||||
},
|
||||
plugins,
|
||||
plugin: {
|
||||
registered: registeredPlugins
|
||||
},
|
||||
theme: {
|
||||
registered: registeredThemes,
|
||||
default: defaultTheme
|
||||
},
|
||||
email: {
|
||||
enabled: Emailer.isEnabled()
|
||||
},
|
||||
|
@ -240,6 +250,9 @@ function customConfig (): CustomConfig {
|
|||
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
default: CONFIG.THEME.DEFAULT
|
||||
},
|
||||
services: {
|
||||
twitter: {
|
||||
username: CONFIG.SERVICES.TWITTER.USERNAME,
|
||||
|
|
|
@ -183,6 +183,7 @@ async function updateMe (req: express.Request, res: express.Response) {
|
|||
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
|
||||
if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
|
||||
if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages
|
||||
if (body.theme !== undefined) user.theme = body.theme
|
||||
|
||||
if (body.email !== undefined) {
|
||||
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type'
|
|||
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
||||
import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model'
|
||||
import { isUrlValid } from './activitypub/misc'
|
||||
import { isThemeRegistered } from '../../lib/plugins/theme-utils'
|
||||
|
||||
const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS
|
||||
|
||||
|
@ -61,6 +62,10 @@ function isCSSPathsValid (css: any[]) {
|
|||
return isArray(css) && css.every(c => isSafePath(c))
|
||||
}
|
||||
|
||||
function isThemeValid (name: string) {
|
||||
return isPluginNameValid(name) && isThemeRegistered(name)
|
||||
}
|
||||
|
||||
function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) {
|
||||
return isNpmPluginNameValid(packageJSON.name) &&
|
||||
isPluginDescriptionValid(packageJSON.description) &&
|
||||
|
@ -82,6 +87,7 @@ function isLibraryCodeValid (library: any) {
|
|||
export {
|
||||
isPluginTypeValid,
|
||||
isPackageJSONValid,
|
||||
isThemeValid,
|
||||
isPluginVersionValid,
|
||||
isPluginNameValid,
|
||||
isPluginDescriptionValid,
|
||||
|
|
|
@ -29,7 +29,8 @@ function checkMissedConfig () {
|
|||
'followers.instance.enabled', 'followers.instance.manual_approval',
|
||||
'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces',
|
||||
'history.videos.max_age', 'views.videos.remote.max_age',
|
||||
'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max'
|
||||
'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max',
|
||||
'theme.default'
|
||||
]
|
||||
const requiredAlternatives = [
|
||||
[ // set
|
||||
|
|
|
@ -224,6 +224,9 @@ const CONFIG = {
|
|||
get ENABLED () { return config.get<boolean>('followers.instance.enabled') },
|
||||
get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') }
|
||||
}
|
||||
},
|
||||
THEME: {
|
||||
get DEFAULT () { return config.get<string>('theme.default') }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 395
|
||||
const LAST_MIGRATION_VERSION = 400
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -585,6 +585,8 @@ const P2P_MEDIA_LOADER_PEER_VERSION = 2
|
|||
const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css'
|
||||
const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME)
|
||||
|
||||
const DEFAULT_THEME = 'default'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Special constants for a test instance
|
||||
|
@ -667,6 +669,7 @@ export {
|
|||
HLS_STREAMING_PLAYLIST_DIRECTORY,
|
||||
FEEDS,
|
||||
JOB_TTL,
|
||||
DEFAULT_THEME,
|
||||
NSFW_POLICY_TYPES,
|
||||
STATIC_MAX_AGE,
|
||||
STATIC_PATHS,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
queryInterface: Sequelize.QueryInterface,
|
||||
sequelize: Sequelize.Sequelize,
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'default'
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'theme', data)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -11,6 +11,7 @@ import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants'
|
|||
import { PluginType } from '../../../shared/models/plugins/plugin.type'
|
||||
import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn'
|
||||
import { outputFile } from 'fs-extra'
|
||||
import { ServerConfigPlugin } from '../../../shared/models/server'
|
||||
|
||||
export interface RegisteredPlugin {
|
||||
name: string
|
||||
|
@ -47,7 +48,7 @@ export class PluginManager {
|
|||
private constructor () {
|
||||
}
|
||||
|
||||
async registerPlugins () {
|
||||
async registerPluginsAndThemes () {
|
||||
await this.resetCSSGlobalFile()
|
||||
|
||||
const plugins = await PluginModel.listEnabledPluginsAndThemes()
|
||||
|
@ -63,12 +64,20 @@ export class PluginManager {
|
|||
this.sortHooksByPriority()
|
||||
}
|
||||
|
||||
getRegisteredPluginOrTheme (name: string) {
|
||||
return this.registeredPlugins[name]
|
||||
}
|
||||
|
||||
getRegisteredPlugin (name: string) {
|
||||
return this.registeredPlugins[ name ]
|
||||
const registered = this.getRegisteredPluginOrTheme(name)
|
||||
|
||||
if (!registered || registered.type !== PluginType.PLUGIN) return undefined
|
||||
|
||||
return registered
|
||||
}
|
||||
|
||||
getRegisteredTheme (name: string) {
|
||||
const registered = this.getRegisteredPlugin(name)
|
||||
const registered = this.getRegisteredPluginOrTheme(name)
|
||||
|
||||
if (!registered || registered.type !== PluginType.THEME) return undefined
|
||||
|
||||
|
@ -76,7 +85,11 @@ export class PluginManager {
|
|||
}
|
||||
|
||||
getRegisteredPlugins () {
|
||||
return this.registeredPlugins
|
||||
return this.getRegisteredPluginsOrThemes(PluginType.PLUGIN)
|
||||
}
|
||||
|
||||
getRegisteredThemes () {
|
||||
return this.getRegisteredPluginsOrThemes(PluginType.THEME)
|
||||
}
|
||||
|
||||
async runHook (hookName: string, param?: any) {
|
||||
|
@ -309,6 +322,19 @@ export class PluginManager {
|
|||
}
|
||||
}
|
||||
|
||||
private getRegisteredPluginsOrThemes (type: PluginType) {
|
||||
const plugins: RegisteredPlugin[] = []
|
||||
|
||||
for (const pluginName of Object.keys(this.registeredPlugins)) {
|
||||
const plugin = this.registeredPlugins[ pluginName ]
|
||||
if (plugin.type !== type) continue
|
||||
|
||||
plugins.push(plugin)
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
static get Instance () {
|
||||
return this.instance || (this.instance = new this())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { DEFAULT_THEME } from '../../initializers/constants'
|
||||
import { PluginManager } from './plugin-manager'
|
||||
import { CONFIG } from '../../initializers/config'
|
||||
|
||||
function getThemeOrDefault (name: string) {
|
||||
if (isThemeRegistered(name)) return name
|
||||
|
||||
// Fallback to admin default theme
|
||||
if (name !== CONFIG.THEME.DEFAULT) return getThemeOrDefault(CONFIG.THEME.DEFAULT)
|
||||
|
||||
return DEFAULT_THEME
|
||||
}
|
||||
|
||||
function isThemeRegistered (name: string) {
|
||||
if (name === DEFAULT_THEME) return true
|
||||
|
||||
return !!PluginManager.Instance.getRegisteredThemes()
|
||||
.find(r => r.name === name)
|
||||
}
|
||||
|
||||
export {
|
||||
getThemeOrDefault,
|
||||
isThemeRegistered
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import * as express from 'express'
|
||||
import { body } from 'express-validator/check'
|
||||
import { isUserNSFWPolicyValid, isUserVideoQuotaValid, isUserVideoQuotaDailyValid } from '../../helpers/custom-validators/users'
|
||||
import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
||||
import { Emailer } from '../../lib/emailer'
|
||||
import { areValidationErrors } from './utils'
|
||||
import { isThemeValid } from '../../helpers/custom-validators/plugins'
|
||||
|
||||
const customConfigUpdateValidator = [
|
||||
body('instance.name').exists().withMessage('Should have a valid instance name'),
|
||||
|
@ -47,6 +48,8 @@ const customConfigUpdateValidator = [
|
|||
body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'),
|
||||
body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'),
|
||||
|
||||
body('theme.default').custom(isThemeValid).withMessage('Should have a valid theme'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body })
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const servePluginStaticDirectoryValidator = [
|
|||
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
const plugin = PluginManager.Instance.getRegisteredPlugin(req.params.pluginName)
|
||||
const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(req.params.pluginName)
|
||||
|
||||
if (!plugin || plugin.version !== req.params.pluginVersion) {
|
||||
return res.sendStatus(404)
|
||||
|
|
|
@ -28,6 +28,7 @@ import { ActorModel } from '../../models/activitypub/actor'
|
|||
import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
|
||||
import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
|
||||
import { UserRegister } from '../../../shared/models/users/user-register.model'
|
||||
import { isThemeValid } from '../../helpers/custom-validators/plugins'
|
||||
|
||||
const usersAddValidator = [
|
||||
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
|
||||
|
@ -204,6 +205,9 @@ const usersUpdateMeValidator = [
|
|||
body('videosHistoryEnabled')
|
||||
.optional()
|
||||
.custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
|
||||
body('theme')
|
||||
.optional()
|
||||
.custom(isThemeValid).withMessage('Should have a valid theme'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
|
||||
|
|
|
@ -44,7 +44,7 @@ import { VideoChannelModel } from '../video/video-channel'
|
|||
import { AccountModel } from './account'
|
||||
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
|
||||
import { values } from 'lodash'
|
||||
import { NSFW_POLICY_TYPES } from '../../initializers/constants'
|
||||
import { DEFAULT_THEME, NSFW_POLICY_TYPES } from '../../initializers/constants'
|
||||
import { clearCacheByUserId } from '../../lib/oauth-model'
|
||||
import { UserNotificationSettingModel } from './user-notification-setting'
|
||||
import { VideoModel } from '../video/video'
|
||||
|
@ -52,6 +52,8 @@ import { ActorModel } from '../activitypub/actor'
|
|||
import { ActorFollowModel } from '../activitypub/actor-follow'
|
||||
import { VideoImportModel } from '../video/video-import'
|
||||
import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
|
||||
import { isThemeValid } from '../../helpers/custom-validators/plugins'
|
||||
import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
|
||||
|
||||
enum ScopeNames {
|
||||
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
|
||||
|
@ -187,6 +189,12 @@ export class UserModel extends Model<UserModel> {
|
|||
@Column(DataType.BIGINT)
|
||||
videoQuotaDaily: number
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(DEFAULT_THEME)
|
||||
@Is('UserTheme', value => throwIfNotValid(value, isThemeValid, 'theme'))
|
||||
@Column
|
||||
theme: string
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
|
@ -560,6 +568,7 @@ export class UserModel extends Model<UserModel> {
|
|||
autoPlayVideo: this.autoPlayVideo,
|
||||
videoLanguages: this.videoLanguages,
|
||||
role: this.role,
|
||||
theme: getThemeOrDefault(this.theme),
|
||||
roleLabel: USER_ROLE_LABELS[ this.role ],
|
||||
videoQuota: this.videoQuota,
|
||||
videoQuotaDaily: this.videoQuotaDaily,
|
||||
|
|
|
@ -27,6 +27,9 @@ describe('Test config API validators', function () {
|
|||
css: 'body { background-color: red; }'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
default: 'default'
|
||||
},
|
||||
services: {
|
||||
twitter: {
|
||||
username: '@MySuperUsername',
|
||||
|
|
|
@ -190,6 +190,9 @@ describe('Test config', function () {
|
|||
css: 'body { background-color: red; }'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
default: 'default'
|
||||
},
|
||||
services: {
|
||||
twitter: {
|
||||
username: '@Kuja',
|
||||
|
|
|
@ -59,6 +59,9 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
|
|||
css: 'body { background-color: red; }'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
default: 'default'
|
||||
},
|
||||
services: {
|
||||
twitter: {
|
||||
username: '@MySuperUsername',
|
||||
|
|
|
@ -15,6 +15,10 @@ export interface CustomConfig {
|
|||
}
|
||||
}
|
||||
|
||||
theme: {
|
||||
default: string
|
||||
}
|
||||
|
||||
services: {
|
||||
twitter: {
|
||||
username: string
|
||||
|
|
|
@ -24,7 +24,14 @@ export interface ServerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
plugins: ServerConfigPlugin[]
|
||||
plugin: {
|
||||
registered: ServerConfigPlugin[]
|
||||
}
|
||||
|
||||
theme: {
|
||||
registered: ServerConfigPlugin[]
|
||||
default: string
|
||||
}
|
||||
|
||||
email: {
|
||||
enabled: boolean
|
||||
|
|
|
@ -13,4 +13,6 @@ export interface UserUpdateMe {
|
|||
email?: string
|
||||
currentPassword?: string
|
||||
password?: string
|
||||
|
||||
theme?: string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue