diff options
Diffstat (limited to 'client/src/app/core')
-rw-r--r-- | client/src/app/core/core.module.ts | 7 | ||||
-rw-r--r-- | client/src/app/core/menu/menu.service.ts | 58 | ||||
-rw-r--r-- | client/src/app/core/renderer/html-renderer.service.ts | 10 | ||||
-rw-r--r-- | client/src/app/core/renderer/markdown.service.ts | 53 | ||||
-rw-r--r-- | client/src/app/core/routing/index.ts | 2 | ||||
-rw-r--r-- | client/src/app/core/routing/meta-guard.service.ts | 23 | ||||
-rw-r--r-- | client/src/app/core/routing/meta.service.ts | 40 | ||||
-rw-r--r-- | client/src/app/core/routing/redirect.service.ts | 35 | ||||
-rw-r--r-- | client/src/app/core/server/server.service.ts | 32 | ||||
-rw-r--r-- | client/src/app/core/theme/theme.service.ts | 14 |
10 files changed, 215 insertions, 59 deletions
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 3152a7003..de3274544 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts | |||
@@ -14,7 +14,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard' | |||
14 | import { Notifier } from './notification' | 14 | import { Notifier } from './notification' |
15 | import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer' | 15 | import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer' |
16 | import { RestExtractor, RestService } from './rest' | 16 | import { RestExtractor, RestService } from './rest' |
17 | import { LoginGuard, RedirectService, UnloggedGuard, UserRightGuard } from './routing' | 17 | import { LoginGuard, MetaGuard, MetaService, RedirectService, UnloggedGuard, UserRightGuard } from './routing' |
18 | import { CanDeactivateGuard } from './routing/can-deactivate-guard.service' | 18 | import { CanDeactivateGuard } from './routing/can-deactivate-guard.service' |
19 | import { ServerConfigResolver } from './routing/server-config-resolver.service' | 19 | import { ServerConfigResolver } from './routing/server-config-resolver.service' |
20 | import { ScopedTokensService } from './scoped-tokens' | 20 | import { ScopedTokensService } from './scoped-tokens' |
@@ -77,7 +77,10 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra | |||
77 | MessageService, | 77 | MessageService, |
78 | PeerTubeSocket, | 78 | PeerTubeSocket, |
79 | ServerConfigResolver, | 79 | ServerConfigResolver, |
80 | CanDeactivateGuard | 80 | CanDeactivateGuard, |
81 | |||
82 | MetaService, | ||
83 | MetaGuard | ||
81 | ] | 84 | ] |
82 | }) | 85 | }) |
83 | export class CoreModule { | 86 | export class CoreModule { |
diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index 502d3bb2f..77592cbb6 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts | |||
@@ -1,8 +1,19 @@ | |||
1 | import { fromEvent } from 'rxjs' | 1 | import { fromEvent } from 'rxjs' |
2 | import { debounceTime } from 'rxjs/operators' | 2 | import { debounceTime } from 'rxjs/operators' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { GlobalIconName } from '@app/shared/shared-icons' | ||
5 | import { sortObjectComparator } from '@shared/core-utils/miscs/miscs' | ||
6 | import { ServerConfig } from '@shared/models/server' | ||
4 | import { ScreenService } from '../wrappers' | 7 | import { ScreenService } from '../wrappers' |
5 | 8 | ||
9 | export type MenuLink = { | ||
10 | icon: GlobalIconName | ||
11 | label: string | ||
12 | menuLabel: string | ||
13 | path: string | ||
14 | priority: number | ||
15 | } | ||
16 | |||
6 | @Injectable() | 17 | @Injectable() |
7 | export class MenuService { | 18 | export class MenuService { |
8 | isMenuDisplayed = true | 19 | isMenuDisplayed = true |
@@ -48,6 +59,53 @@ export class MenuService { | |||
48 | this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser | 59 | this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser |
49 | } | 60 | } |
50 | 61 | ||
62 | buildCommonLinks (config: ServerConfig) { | ||
63 | let entries: MenuLink[] = [ | ||
64 | { | ||
65 | icon: 'globe' as 'globe', | ||
66 | label: $localize`Discover videos`, | ||
67 | menuLabel: $localize`Discover`, | ||
68 | path: '/videos/overview', | ||
69 | priority: 150 | ||
70 | }, | ||
71 | { | ||
72 | icon: 'trending' as 'trending', | ||
73 | label: $localize`Trending videos`, | ||
74 | menuLabel: $localize`Trending`, | ||
75 | path: '/videos/trending', | ||
76 | priority: 140 | ||
77 | }, | ||
78 | { | ||
79 | icon: 'recently-added' as 'recently-added', | ||
80 | label: $localize`Recently added videos`, | ||
81 | menuLabel: $localize`Recently added`, | ||
82 | path: '/videos/recently-added', | ||
83 | priority: 130 | ||
84 | }, | ||
85 | { | ||
86 | icon: 'octagon' as 'octagon', | ||
87 | label: $localize`Local videos`, | ||
88 | menuLabel: $localize`Local videos`, | ||
89 | path: '/videos/local', | ||
90 | priority: 120 | ||
91 | } | ||
92 | ] | ||
93 | |||
94 | if (config.homepage.enabled) { | ||
95 | entries.push({ | ||
96 | icon: 'home' as 'home', | ||
97 | label: $localize`Home`, | ||
98 | menuLabel: $localize`Home`, | ||
99 | path: '/home', | ||
100 | priority: 160 | ||
101 | }) | ||
102 | } | ||
103 | |||
104 | entries = entries.sort(sortObjectComparator('priority', 'desc')) | ||
105 | |||
106 | return entries | ||
107 | } | ||
108 | |||
51 | private handleWindowResize () { | 109 | private handleWindowResize () { |
52 | // On touch screens, do not handle window resize event since opened menu is handled with a content overlay | 110 | // On touch screens, do not handle window resize event since opened menu is handled with a content overlay |
53 | if (this.screenService.isInTouchScreen()) return | 111 | if (this.screenService.isInTouchScreen()) return |
diff --git a/client/src/app/core/renderer/html-renderer.service.ts b/client/src/app/core/renderer/html-renderer.service.ts index 3176cf6a4..418d8603e 100644 --- a/client/src/app/core/renderer/html-renderer.service.ts +++ b/client/src/app/core/renderer/html-renderer.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { LinkifierService } from './linkifier.service' | 2 | import { LinkifierService } from './linkifier.service' |
3 | import { SANITIZE_OPTIONS } from '@shared/core-utils/renderer/html' | 3 | import { getCustomMarkupSanitizeOptions, getSanitizeOptions } from '@shared/core-utils/renderer/html' |
4 | 4 | ||
5 | @Injectable() | 5 | @Injectable() |
6 | export class HtmlRendererService { | 6 | export class HtmlRendererService { |
@@ -20,7 +20,7 @@ export class HtmlRendererService { | |||
20 | }) | 20 | }) |
21 | } | 21 | } |
22 | 22 | ||
23 | async toSafeHtml (text: string) { | 23 | async toSafeHtml (text: string, additionalAllowedTags: string[] = []) { |
24 | const [ html ] = await Promise.all([ | 24 | const [ html ] = await Promise.all([ |
25 | // Convert possible markdown to html | 25 | // Convert possible markdown to html |
26 | this.linkifier.linkify(text), | 26 | this.linkifier.linkify(text), |
@@ -28,7 +28,11 @@ export class HtmlRendererService { | |||
28 | this.loadSanitizeHtml() | 28 | this.loadSanitizeHtml() |
29 | ]) | 29 | ]) |
30 | 30 | ||
31 | return this.sanitizeHtml(html, SANITIZE_OPTIONS) | 31 | const options = additionalAllowedTags.length !== 0 |
32 | ? getCustomMarkupSanitizeOptions(additionalAllowedTags) | ||
33 | : getSanitizeOptions() | ||
34 | |||
35 | return this.sanitizeHtml(html, options) | ||
32 | } | 36 | } |
33 | 37 | ||
34 | private async loadSanitizeHtml () { | 38 | private async loadSanitizeHtml () { |
diff --git a/client/src/app/core/renderer/markdown.service.ts b/client/src/app/core/renderer/markdown.service.ts index edddb0a66..ca1bf4eb9 100644 --- a/client/src/app/core/renderer/markdown.service.ts +++ b/client/src/app/core/renderer/markdown.service.ts | |||
@@ -17,12 +17,15 @@ type MarkdownParsers = { | |||
17 | enhancedMarkdownIt: MarkdownIt | 17 | enhancedMarkdownIt: MarkdownIt |
18 | enhancedWithHTMLMarkdownIt: MarkdownIt | 18 | enhancedWithHTMLMarkdownIt: MarkdownIt |
19 | 19 | ||
20 | completeMarkdownIt: MarkdownIt | 20 | unsafeMarkdownIt: MarkdownIt |
21 | |||
22 | customPageMarkdownIt: MarkdownIt | ||
21 | } | 23 | } |
22 | 24 | ||
23 | type MarkdownConfig = { | 25 | type MarkdownConfig = { |
24 | rules: string[] | 26 | rules: string[] |
25 | html: boolean | 27 | html: boolean |
28 | breaks: boolean | ||
26 | escape?: boolean | 29 | escape?: boolean |
27 | } | 30 | } |
28 | 31 | ||
@@ -35,18 +38,24 @@ export class MarkdownService { | |||
35 | private markdownParsers: MarkdownParsers = { | 38 | private markdownParsers: MarkdownParsers = { |
36 | textMarkdownIt: null, | 39 | textMarkdownIt: null, |
37 | textWithHTMLMarkdownIt: null, | 40 | textWithHTMLMarkdownIt: null, |
41 | |||
38 | enhancedMarkdownIt: null, | 42 | enhancedMarkdownIt: null, |
39 | enhancedWithHTMLMarkdownIt: null, | 43 | enhancedWithHTMLMarkdownIt: null, |
40 | completeMarkdownIt: null | 44 | |
45 | unsafeMarkdownIt: null, | ||
46 | |||
47 | customPageMarkdownIt: null | ||
41 | } | 48 | } |
42 | private parsersConfig: MarkdownParserConfigs = { | 49 | private parsersConfig: MarkdownParserConfigs = { |
43 | textMarkdownIt: { rules: TEXT_RULES, html: false }, | 50 | textMarkdownIt: { rules: TEXT_RULES, breaks: true, html: false }, |
44 | textWithHTMLMarkdownIt: { rules: TEXT_WITH_HTML_RULES, html: true, escape: true }, | 51 | textWithHTMLMarkdownIt: { rules: TEXT_WITH_HTML_RULES, breaks: true, html: true, escape: true }, |
45 | 52 | ||
46 | enhancedMarkdownIt: { rules: ENHANCED_RULES, html: false }, | 53 | enhancedMarkdownIt: { rules: ENHANCED_RULES, breaks: true, html: false }, |
47 | enhancedWithHTMLMarkdownIt: { rules: ENHANCED_WITH_HTML_RULES, html: true, escape: true }, | 54 | enhancedWithHTMLMarkdownIt: { rules: ENHANCED_WITH_HTML_RULES, breaks: true, html: true, escape: true }, |
48 | 55 | ||
49 | completeMarkdownIt: { rules: COMPLETE_RULES, html: true } | 56 | unsafeMarkdownIt: { rules: COMPLETE_RULES, breaks: true, html: true, escape: false }, |
57 | |||
58 | customPageMarkdownIt: { rules: COMPLETE_RULES, breaks: false, html: true, escape: true } | ||
50 | } | 59 | } |
51 | 60 | ||
52 | private emojiModule: any | 61 | private emojiModule: any |
@@ -54,22 +63,26 @@ export class MarkdownService { | |||
54 | constructor (private htmlRenderer: HtmlRendererService) {} | 63 | constructor (private htmlRenderer: HtmlRendererService) {} |
55 | 64 | ||
56 | textMarkdownToHTML (markdown: string, withHtml = false, withEmoji = false) { | 65 | textMarkdownToHTML (markdown: string, withHtml = false, withEmoji = false) { |
57 | if (withHtml) return this.render('textWithHTMLMarkdownIt', markdown, withEmoji) | 66 | if (withHtml) return this.render({ name: 'textWithHTMLMarkdownIt', markdown, withEmoji }) |
58 | 67 | ||
59 | return this.render('textMarkdownIt', markdown, withEmoji) | 68 | return this.render({ name: 'textMarkdownIt', markdown, withEmoji }) |
60 | } | 69 | } |
61 | 70 | ||
62 | enhancedMarkdownToHTML (markdown: string, withHtml = false, withEmoji = false) { | 71 | enhancedMarkdownToHTML (markdown: string, withHtml = false, withEmoji = false) { |
63 | if (withHtml) return this.render('enhancedWithHTMLMarkdownIt', markdown, withEmoji) | 72 | if (withHtml) return this.render({ name: 'enhancedWithHTMLMarkdownIt', markdown, withEmoji }) |
73 | |||
74 | return this.render({ name: 'enhancedMarkdownIt', markdown, withEmoji }) | ||
75 | } | ||
64 | 76 | ||
65 | return this.render('enhancedMarkdownIt', markdown, withEmoji) | 77 | unsafeMarkdownToHTML (markdown: string, _trustedInput: true) { |
78 | return this.render({ name: 'unsafeMarkdownIt', markdown, withEmoji: true }) | ||
66 | } | 79 | } |
67 | 80 | ||
68 | completeMarkdownToHTML (markdown: string) { | 81 | customPageMarkdownToHTML (markdown: string, additionalAllowedTags: string[]) { |
69 | return this.render('completeMarkdownIt', markdown, true) | 82 | return this.render({ name: 'customPageMarkdownIt', markdown, withEmoji: true, additionalAllowedTags }) |
70 | } | 83 | } |
71 | 84 | ||
72 | async processVideoTimestamps (html: string) { | 85 | processVideoTimestamps (html: string) { |
73 | return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) { | 86 | return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) { |
74 | const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0)) | 87 | const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0)) |
75 | const url = buildVideoLink({ startTime: t }) | 88 | const url = buildVideoLink({ startTime: t }) |
@@ -77,7 +90,13 @@ export class MarkdownService { | |||
77 | }) | 90 | }) |
78 | } | 91 | } |
79 | 92 | ||
80 | private async render (name: keyof MarkdownParsers, markdown: string, withEmoji = false) { | 93 | private async render (options: { |
94 | name: keyof MarkdownParsers | ||
95 | markdown: string | ||
96 | withEmoji: boolean | ||
97 | additionalAllowedTags?: string[] | ||
98 | }) { | ||
99 | const { name, markdown, withEmoji, additionalAllowedTags } = options | ||
81 | if (!markdown) return '' | 100 | if (!markdown) return '' |
82 | 101 | ||
83 | const config = this.parsersConfig[ name ] | 102 | const config = this.parsersConfig[ name ] |
@@ -96,7 +115,7 @@ export class MarkdownService { | |||
96 | let html = this.markdownParsers[ name ].render(markdown) | 115 | let html = this.markdownParsers[ name ].render(markdown) |
97 | html = this.avoidTruncatedTags(html) | 116 | html = this.avoidTruncatedTags(html) |
98 | 117 | ||
99 | if (config.escape) return this.htmlRenderer.toSafeHtml(html) | 118 | if (config.escape) return this.htmlRenderer.toSafeHtml(html, additionalAllowedTags) |
100 | 119 | ||
101 | return html | 120 | return html |
102 | } | 121 | } |
@@ -105,7 +124,7 @@ export class MarkdownService { | |||
105 | // FIXME: import('...') returns a struct module, containing a "default" field | 124 | // FIXME: import('...') returns a struct module, containing a "default" field |
106 | const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default | 125 | const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default |
107 | 126 | ||
108 | const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html: config.html }) | 127 | const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: config.breaks, html: config.html }) |
109 | 128 | ||
110 | for (const rule of config.rules) { | 129 | for (const rule of config.rules) { |
111 | markdownIt.enable(rule) | 130 | markdownIt.enable(rule) |
diff --git a/client/src/app/core/routing/index.ts b/client/src/app/core/routing/index.ts index 239c27caf..4314ea475 100644 --- a/client/src/app/core/routing/index.ts +++ b/client/src/app/core/routing/index.ts | |||
@@ -3,6 +3,8 @@ export * from './custom-reuse-strategy' | |||
3 | export * from './disable-for-reuse-hook' | 3 | export * from './disable-for-reuse-hook' |
4 | export * from './login-guard.service' | 4 | export * from './login-guard.service' |
5 | export * from './menu-guard.service' | 5 | export * from './menu-guard.service' |
6 | export * from './meta-guard.service' | ||
7 | export * from './meta.service' | ||
6 | export * from './preload-selected-modules-list' | 8 | export * from './preload-selected-modules-list' |
7 | export * from './redirect.service' | 9 | export * from './redirect.service' |
8 | export * from './server-config-resolver.service' | 10 | export * from './server-config-resolver.service' |
diff --git a/client/src/app/core/routing/meta-guard.service.ts b/client/src/app/core/routing/meta-guard.service.ts new file mode 100644 index 000000000..bedb3450e --- /dev/null +++ b/client/src/app/core/routing/meta-guard.service.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router' | ||
3 | import { MetaService } from './meta.service' | ||
4 | |||
5 | @Injectable() | ||
6 | export class MetaGuard implements CanActivate, CanActivateChild { | ||
7 | |||
8 | constructor (private meta: MetaService) { } | ||
9 | |||
10 | canActivate (route: ActivatedRouteSnapshot): boolean { | ||
11 | const metaSettings = route.data?.meta | ||
12 | |||
13 | if (metaSettings) { | ||
14 | this.meta.update(metaSettings) | ||
15 | } | ||
16 | |||
17 | return true | ||
18 | } | ||
19 | |||
20 | canActivateChild (route: ActivatedRouteSnapshot): boolean { | ||
21 | return this.canActivate(route) | ||
22 | } | ||
23 | } | ||
diff --git a/client/src/app/core/routing/meta.service.ts b/client/src/app/core/routing/meta.service.ts new file mode 100644 index 000000000..a5ac778dc --- /dev/null +++ b/client/src/app/core/routing/meta.service.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Meta, Title } from '@angular/platform-browser' | ||
3 | import { HTMLServerConfig } from '@shared/models/server' | ||
4 | import { ServerService } from '../server' | ||
5 | |||
6 | export interface MetaSettings { | ||
7 | title?: string | ||
8 | } | ||
9 | |||
10 | @Injectable() | ||
11 | export class MetaService { | ||
12 | private config: HTMLServerConfig | ||
13 | |||
14 | constructor ( | ||
15 | private titleService: Title, | ||
16 | private meta: Meta, | ||
17 | private server: ServerService | ||
18 | ) { | ||
19 | this.config = this.server.getTmpConfig() | ||
20 | this.server.getConfig() | ||
21 | .subscribe(config => this.config = config) | ||
22 | } | ||
23 | |||
24 | setTitle (subTitle?: string) { | ||
25 | let title = '' | ||
26 | if (subTitle) title += `${subTitle} - ` | ||
27 | |||
28 | title += this.config.instance.name | ||
29 | |||
30 | this.titleService.setTitle(title) | ||
31 | } | ||
32 | |||
33 | setTag (name: string, value: string) { | ||
34 | this.meta.addTag({ name, content: value }) | ||
35 | } | ||
36 | |||
37 | update (meta: MetaSettings) { | ||
38 | this.setTitle(meta.title) | ||
39 | } | ||
40 | } | ||
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts index 6d26fb504..cf690a4d0 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts | |||
@@ -6,14 +6,14 @@ import { ServerService } from '../server' | |||
6 | export class RedirectService { | 6 | export class RedirectService { |
7 | // Default route could change according to the instance configuration | 7 | // Default route could change according to the instance configuration |
8 | static INIT_DEFAULT_ROUTE = '/videos/trending' | 8 | static INIT_DEFAULT_ROUTE = '/videos/trending' |
9 | static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE | ||
10 | static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed' | 9 | static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed' |
11 | static DEFAULT_TRENDING_ALGORITHM = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM | ||
12 | 10 | ||
13 | private previousUrl: string | 11 | private previousUrl: string |
14 | private currentUrl: string | 12 | private currentUrl: string |
15 | 13 | ||
16 | private redirectingToHomepage = false | 14 | private redirectingToHomepage = false |
15 | private defaultTrendingAlgorithm = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM | ||
16 | private defaultRoute = RedirectService.INIT_DEFAULT_ROUTE | ||
17 | 17 | ||
18 | constructor ( | 18 | constructor ( |
19 | private router: Router, | 19 | private router: Router, |
@@ -22,10 +22,10 @@ export class RedirectService { | |||
22 | // The config is first loaded from the cache so try to get the default route | 22 | // The config is first loaded from the cache so try to get the default route |
23 | const tmpConfig = this.serverService.getTmpConfig() | 23 | const tmpConfig = this.serverService.getTmpConfig() |
24 | if (tmpConfig?.instance?.defaultClientRoute) { | 24 | if (tmpConfig?.instance?.defaultClientRoute) { |
25 | RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute | 25 | this.defaultRoute = tmpConfig.instance.defaultClientRoute |
26 | } | 26 | } |
27 | if (tmpConfig?.trending?.videos?.algorithms?.default) { | 27 | if (tmpConfig?.trending?.videos?.algorithms?.default) { |
28 | RedirectService.DEFAULT_TRENDING_ALGORITHM = tmpConfig.trending.videos.algorithms.default | 28 | this.defaultTrendingAlgorithm = tmpConfig.trending.videos.algorithms.default |
29 | } | 29 | } |
30 | 30 | ||
31 | // Load default route | 31 | // Load default route |
@@ -34,13 +34,8 @@ export class RedirectService { | |||
34 | const defaultRouteConfig = config.instance.defaultClientRoute | 34 | const defaultRouteConfig = config.instance.defaultClientRoute |
35 | const defaultTrendingConfig = config.trending.videos.algorithms.default | 35 | const defaultTrendingConfig = config.trending.videos.algorithms.default |
36 | 36 | ||
37 | if (defaultRouteConfig) { | 37 | if (defaultRouteConfig) this.defaultRoute = defaultRouteConfig |
38 | RedirectService.DEFAULT_ROUTE = defaultRouteConfig | 38 | if (defaultTrendingConfig) this.defaultTrendingAlgorithm = defaultTrendingConfig |
39 | } | ||
40 | |||
41 | if (defaultTrendingConfig) { | ||
42 | RedirectService.DEFAULT_TRENDING_ALGORITHM = defaultTrendingConfig | ||
43 | } | ||
44 | }) | 39 | }) |
45 | 40 | ||
46 | // Track previous url | 41 | // Track previous url |
@@ -53,6 +48,14 @@ export class RedirectService { | |||
53 | }) | 48 | }) |
54 | } | 49 | } |
55 | 50 | ||
51 | getDefaultRoute () { | ||
52 | return this.defaultRoute | ||
53 | } | ||
54 | |||
55 | getDefaultTrendingAlgorithm () { | ||
56 | return this.defaultTrendingAlgorithm | ||
57 | } | ||
58 | |||
56 | redirectToPreviousRoute () { | 59 | redirectToPreviousRoute () { |
57 | const exceptions = [ | 60 | const exceptions = [ |
58 | '/verify-account', | 61 | '/verify-account', |
@@ -72,21 +75,21 @@ export class RedirectService { | |||
72 | 75 | ||
73 | this.redirectingToHomepage = true | 76 | this.redirectingToHomepage = true |
74 | 77 | ||
75 | console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE) | 78 | console.log('Redirecting to %s...', this.defaultRoute) |
76 | 79 | ||
77 | this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) | 80 | this.router.navigateByUrl(this.defaultRoute, { skipLocationChange }) |
78 | .then(() => this.redirectingToHomepage = false) | 81 | .then(() => this.redirectingToHomepage = false) |
79 | .catch(() => { | 82 | .catch(() => { |
80 | this.redirectingToHomepage = false | 83 | this.redirectingToHomepage = false |
81 | 84 | ||
82 | console.error( | 85 | console.error( |
83 | 'Cannot navigate to %s, resetting default route to %s.', | 86 | 'Cannot navigate to %s, resetting default route to %s.', |
84 | RedirectService.DEFAULT_ROUTE, | 87 | this.defaultRoute, |
85 | RedirectService.INIT_DEFAULT_ROUTE | 88 | RedirectService.INIT_DEFAULT_ROUTE |
86 | ) | 89 | ) |
87 | 90 | ||
88 | RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE | 91 | this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE |
89 | return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) | 92 | return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange }) |
90 | }) | 93 | }) |
91 | 94 | ||
92 | } | 95 | } |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index 906191ae1..5b1b7603f 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -3,7 +3,6 @@ import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators' | |||
3 | import { HttpClient } from '@angular/common/http' | 3 | import { HttpClient } from '@angular/common/http' |
4 | import { Inject, Injectable, LOCALE_ID } from '@angular/core' | 4 | import { Inject, Injectable, LOCALE_ID } from '@angular/core' |
5 | import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers' | 5 | import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers' |
6 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' | ||
7 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' | 6 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' |
8 | import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models' | 7 | import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models' |
9 | import { environment } from '../../../environments/environment' | 8 | import { environment } from '../../../environments/environment' |
@@ -16,8 +15,6 @@ export class ServerService { | |||
16 | private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' | 15 | private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' |
17 | private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats' | 16 | private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats' |
18 | 17 | ||
19 | private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' | ||
20 | |||
21 | configReloaded = new Subject<ServerConfig>() | 18 | configReloaded = new Subject<ServerConfig>() |
22 | 19 | ||
23 | private localeObservable: Observable<any> | 20 | private localeObservable: Observable<any> |
@@ -176,6 +173,9 @@ export class ServerService { | |||
176 | disableLocalSearch: false, | 173 | disableLocalSearch: false, |
177 | isDefaultSearch: false | 174 | isDefaultSearch: false |
178 | } | 175 | } |
176 | }, | ||
177 | homepage: { | ||
178 | enabled: false | ||
179 | } | 179 | } |
180 | } | 180 | } |
181 | 181 | ||
@@ -201,9 +201,7 @@ export class ServerService { | |||
201 | this.configReset = true | 201 | this.configReset = true |
202 | 202 | ||
203 | // Notify config update | 203 | // Notify config update |
204 | this.getConfig().subscribe(() => { | 204 | return this.getConfig() |
205 | // empty, to fire a reset config event | ||
206 | }) | ||
207 | } | 205 | } |
208 | 206 | ||
209 | getConfig () { | 207 | getConfig () { |
@@ -212,7 +210,6 @@ export class ServerService { | |||
212 | if (!this.configObservable) { | 210 | if (!this.configObservable) { |
213 | this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) | 211 | this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) |
214 | .pipe( | 212 | .pipe( |
215 | tap(config => this.saveConfigLocally(config)), | ||
216 | tap(config => { | 213 | tap(config => { |
217 | this.config = config | 214 | this.config = config |
218 | this.configLoaded = true | 215 | this.configLoaded = true |
@@ -343,20 +340,15 @@ export class ServerService { | |||
343 | ) | 340 | ) |
344 | } | 341 | } |
345 | 342 | ||
346 | private saveConfigLocally (config: ServerConfig) { | ||
347 | peertubeLocalStorage.setItem(ServerService.CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config)) | ||
348 | } | ||
349 | |||
350 | private loadConfigLocally () { | 343 | private loadConfigLocally () { |
351 | const configString = peertubeLocalStorage.getItem(ServerService.CONFIG_LOCAL_STORAGE_KEY) | 344 | const configString = window['PeerTubeServerConfig'] |
352 | 345 | if (!configString) return | |
353 | if (configString) { | 346 | |
354 | try { | 347 | try { |
355 | const parsed = JSON.parse(configString) | 348 | const parsed = JSON.parse(configString) |
356 | Object.assign(this.config, parsed) | 349 | Object.assign(this.config, parsed) |
357 | } catch (err) { | 350 | } catch (err) { |
358 | console.error('Cannot parse config saved in local storage.', err) | 351 | console.error('Cannot parse config saved in from index.html.', err) |
359 | } | ||
360 | } | 352 | } |
361 | } | 353 | } |
362 | } | 354 | } |
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts index 4c4611d01..e7a5ae17a 100644 --- a/client/src/app/core/theme/theme.service.ts +++ b/client/src/app/core/theme/theme.service.ts | |||
@@ -82,7 +82,19 @@ export class ThemeService { | |||
82 | : this.userService.getAnonymousUser().theme | 82 | : this.userService.getAnonymousUser().theme |
83 | 83 | ||
84 | if (theme !== 'instance-default') return theme | 84 | if (theme !== 'instance-default') return theme |
85 | return this.serverConfig.theme.default | 85 | |
86 | const instanceTheme = this.serverConfig.theme.default | ||
87 | if (instanceTheme !== 'default') return instanceTheme | ||
88 | |||
89 | // Default to dark theme if available and wanted by the user | ||
90 | if ( | ||
91 | this.themes.find(t => t.name === 'dark') && | ||
92 | window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches | ||
93 | ) { | ||
94 | return 'dark' | ||
95 | } | ||
96 | |||
97 | return instanceTheme | ||
86 | } | 98 | } |
87 | 99 | ||
88 | private loadTheme (name: string) { | 100 | private loadTheme (name: string) { |