diff options
author | Chocobozzz <me@florianbigard.com> | 2023-01-19 14:52:27 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-01-19 14:52:27 +0100 |
commit | 789ba349318e7ff012590491e76087a1204cccd4 (patch) | |
tree | 69837d320c77260a188b9ee8f59505e540ad505e | |
parent | a4927884b224e3d4d78ff5db88cbb77bbdf8ee0b (diff) | |
download | PeerTube-789ba349318e7ff012590491e76087a1204cccd4.tar.gz PeerTube-789ba349318e7ff012590491e76087a1204cccd4.tar.zst PeerTube-789ba349318e7ff012590491e76087a1204cccd4.zip |
Support mailto links for custom markup
5 files changed, 74 insertions, 59 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index b113df82f..fdd6157e5 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -21,7 +21,7 @@ | |||
21 | 21 | ||
22 | <div class="anchor" id="administrators-and-sustainability"></div> | 22 | <div class="anchor" id="administrators-and-sustainability"></div> |
23 | <a | 23 | <a |
24 | *ngIf="html.administrator || html.maintenanceLifetime || html.businessModel" | 24 | *ngIf="aboutHTML.administrator || aboutHTML.maintenanceLifetime || aboutHTML.businessModel" |
25 | class="anchor-link" | 25 | class="anchor-link" |
26 | routerLink="/about/instance" | 26 | routerLink="/about/instance" |
27 | fragment="administrators-and-sustainability" | 27 | fragment="administrators-and-sustainability" |
@@ -33,7 +33,7 @@ | |||
33 | </h2> | 33 | </h2> |
34 | </a> | 34 | </a> |
35 | 35 | ||
36 | <div class="block administrator" *ngIf="html.administrator"> | 36 | <div class="block administrator" *ngIf="aboutHTML.administrator"> |
37 | <div class="anchor" id="administrators"></div> | 37 | <div class="anchor" id="administrators"></div> |
38 | <a | 38 | <a |
39 | class="anchor-link" | 39 | class="anchor-link" |
@@ -44,10 +44,10 @@ | |||
44 | <h3 i18n class="section-title">Who we are</h3> | 44 | <h3 i18n class="section-title">Who we are</h3> |
45 | </a> | 45 | </a> |
46 | 46 | ||
47 | <div [innerHTML]="html.administrator"></div> | 47 | <div [innerHTML]="aboutHTML.administrator"></div> |
48 | </div> | 48 | </div> |
49 | 49 | ||
50 | <div class="block creation-reason" *ngIf="html.creationReason"> | 50 | <div class="block creation-reason" *ngIf="aboutHTML.creationReason"> |
51 | <div class="anchor" id="creation-reason"></div> | 51 | <div class="anchor" id="creation-reason"></div> |
52 | <a | 52 | <a |
53 | class="anchor-link" | 53 | class="anchor-link" |
@@ -58,10 +58,10 @@ | |||
58 | <h3 i18n class="section-title">Why we created this instance</h3> | 58 | <h3 i18n class="section-title">Why we created this instance</h3> |
59 | </a> | 59 | </a> |
60 | 60 | ||
61 | <div [innerHTML]="html.creationReason"></div> | 61 | <div [innerHTML]="aboutHTML.creationReason"></div> |
62 | </div> | 62 | </div> |
63 | 63 | ||
64 | <div class="block maintenance-lifetime" *ngIf="html.maintenanceLifetime"> | 64 | <div class="block maintenance-lifetime" *ngIf="aboutHTML.maintenanceLifetime"> |
65 | <div class="anchor" id="maintenance-lifetime"></div> | 65 | <div class="anchor" id="maintenance-lifetime"></div> |
66 | <a | 66 | <a |
67 | class="anchor-link" | 67 | class="anchor-link" |
@@ -72,10 +72,10 @@ | |||
72 | <h3 i18n class="section-title">How long we plan to maintain this instance</h3> | 72 | <h3 i18n class="section-title">How long we plan to maintain this instance</h3> |
73 | </a> | 73 | </a> |
74 | 74 | ||
75 | <div [innerHTML]="html.maintenanceLifetime"></div> | 75 | <div [innerHTML]="aboutHTML.maintenanceLifetime"></div> |
76 | </div> | 76 | </div> |
77 | 77 | ||
78 | <div class="block business-model" *ngIf="html.businessModel"> | 78 | <div class="block business-model" *ngIf="aboutHTML.businessModel"> |
79 | <div class="anchor" id="business-model"></div> | 79 | <div class="anchor" id="business-model"></div> |
80 | <a | 80 | <a |
81 | class="anchor-link" | 81 | class="anchor-link" |
@@ -86,12 +86,12 @@ | |||
86 | <h3 i18n class="section-title">How we will pay for keeping our instance running</h3> | 86 | <h3 i18n class="section-title">How we will pay for keeping our instance running</h3> |
87 | </a> | 87 | </a> |
88 | 88 | ||
89 | <div [innerHTML]="html.businessModel"></div> | 89 | <div [innerHTML]="aboutHTML.businessModel"></div> |
90 | </div> | 90 | </div> |
91 | 91 | ||
92 | <div class="anchor" id="information"></div> | 92 | <div class="anchor" id="information"></div> |
93 | <a | 93 | <a |
94 | *ngIf="descriptionContent" | 94 | *ngIf="descriptionElement" |
95 | class="anchor-link" | 95 | class="anchor-link" |
96 | routerLink="/about/instance" | 96 | routerLink="/about/instance" |
97 | fragment="information" | 97 | fragment="information" |
@@ -113,13 +113,13 @@ | |||
113 | <h3 i18n class="section-title">Description</h3> | 113 | <h3 i18n class="section-title">Description</h3> |
114 | </a> | 114 | </a> |
115 | 115 | ||
116 | <my-custom-markup-container [content]="descriptionContent"></my-custom-markup-container> | 116 | <my-custom-markup-container [content]="descriptionElement"></my-custom-markup-container> |
117 | </div> | 117 | </div> |
118 | 118 | ||
119 | <div myPluginSelector pluginSelectorId="about-instance-moderation"> | 119 | <div myPluginSelector pluginSelectorId="about-instance-moderation"> |
120 | <div class="anchor" id="moderation"></div> | 120 | <div class="anchor" id="moderation"></div> |
121 | <a | 121 | <a |
122 | *ngIf="html.moderationInformation || html.codeOfConduct || html.terms" | 122 | *ngIf="aboutHTML.moderationInformation || aboutHTML.codeOfConduct || aboutHTML.terms" |
123 | class="anchor-link" | 123 | class="anchor-link" |
124 | routerLink="/about/instance" | 124 | routerLink="/about/instance" |
125 | fragment="moderation" | 125 | fragment="moderation" |
@@ -130,7 +130,7 @@ | |||
130 | </h2> | 130 | </h2> |
131 | </a> | 131 | </a> |
132 | 132 | ||
133 | <div class="block moderation-information" *ngIf="html.moderationInformation"> | 133 | <div class="block moderation-information" *ngIf="aboutHTML.moderationInformation"> |
134 | <div class="anchor" id="moderation-information"></div> | 134 | <div class="anchor" id="moderation-information"></div> |
135 | <a | 135 | <a |
136 | class="anchor-link" | 136 | class="anchor-link" |
@@ -141,10 +141,10 @@ | |||
141 | <h3 i18n class="section-title">Moderation information</h3> | 141 | <h3 i18n class="section-title">Moderation information</h3> |
142 | </a> | 142 | </a> |
143 | 143 | ||
144 | <div [innerHTML]="html.moderationInformation"></div> | 144 | <div [innerHTML]="aboutHTML.moderationInformation"></div> |
145 | </div> | 145 | </div> |
146 | 146 | ||
147 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> | 147 | <div class="block code-of-conduct" *ngIf="aboutHTML.codeOfConduct"> |
148 | <div class="anchor" id="code-of-conduct"></div> | 148 | <div class="anchor" id="code-of-conduct"></div> |
149 | <a | 149 | <a |
150 | class="anchor-link" | 150 | class="anchor-link" |
@@ -155,7 +155,7 @@ | |||
155 | <h3 i18n class="section-title">Code of conduct</h3> | 155 | <h3 i18n class="section-title">Code of conduct</h3> |
156 | </a> | 156 | </a> |
157 | 157 | ||
158 | <div [innerHTML]="html.codeOfConduct"></div> | 158 | <div [innerHTML]="aboutHTML.codeOfConduct"></div> |
159 | </div> | 159 | </div> |
160 | 160 | ||
161 | <div class="block terms"> | 161 | <div class="block terms"> |
@@ -169,14 +169,14 @@ | |||
169 | <h3 i18n class="section-title">Terms</h3> | 169 | <h3 i18n class="section-title">Terms</h3> |
170 | </a> | 170 | </a> |
171 | 171 | ||
172 | <div [innerHTML]="html.terms"></div> | 172 | <div [innerHTML]="aboutHTML.terms"></div> |
173 | </div> | 173 | </div> |
174 | </div> | 174 | </div> |
175 | 175 | ||
176 | <div myPluginSelector pluginSelectorId="about-instance-other-information"> | 176 | <div myPluginSelector pluginSelectorId="about-instance-other-information"> |
177 | <div class="anchor" id="other-information"></div> | 177 | <div class="anchor" id="other-information"></div> |
178 | <a | 178 | <a |
179 | *ngIf="html.hardwareInformation" | 179 | *ngIf="aboutHTML.hardwareInformation" |
180 | class="anchor-link" | 180 | class="anchor-link" |
181 | routerLink="/about/instance" | 181 | routerLink="/about/instance" |
182 | fragment="other-information" | 182 | fragment="other-information" |
@@ -187,7 +187,7 @@ | |||
187 | </h2> | 187 | </h2> |
188 | </a> | 188 | </a> |
189 | 189 | ||
190 | <div class="block hardware-information" *ngIf="html.hardwareInformation"> | 190 | <div class="block hardware-information" *ngIf="aboutHTML.hardwareInformation"> |
191 | <div class="anchor" id="hardware-information"></div> | 191 | <div class="anchor" id="hardware-information"></div> |
192 | <a | 192 | <a |
193 | class="anchor-link" | 193 | class="anchor-link" |
@@ -198,7 +198,7 @@ | |||
198 | <h3 i18n class="section-title">Hardware information</h3> | 198 | <h3 i18n class="section-title">Hardware information</h3> |
199 | </a> | 199 | </a> |
200 | 200 | ||
201 | <div [innerHTML]="html.hardwareInformation"></div> | 201 | <div [innerHTML]="aboutHTML.hardwareInformation"></div> |
202 | </div> | 202 | </div> |
203 | </div> | 203 | </div> |
204 | </div> | 204 | </div> |
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index 0826bbc5a..e1501d7de 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts | |||
@@ -2,7 +2,7 @@ import { ViewportScroller } from '@angular/common' | |||
2 | import { AfterViewChecked, Component, ElementRef, OnInit, ViewChild } from '@angular/core' | 2 | import { AfterViewChecked, Component, ElementRef, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { Notifier, ServerService } from '@app/core' | 4 | import { Notifier, ServerService } from '@app/core' |
5 | import { InstanceService } from '@app/shared/shared-instance' | 5 | import { AboutHTML } from '@app/shared/shared-instance' |
6 | import { copyToClipboard } from '@root-helpers/utils' | 6 | import { copyToClipboard } from '@root-helpers/utils' |
7 | import { HTMLServerConfig } from '@shared/models/server' | 7 | import { HTMLServerConfig } from '@shared/models/server' |
8 | import { ResolverData } from './about-instance.resolver' | 8 | import { ResolverData } from './about-instance.resolver' |
@@ -17,22 +17,12 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked { | |||
17 | @ViewChild('descriptionWrapper') descriptionWrapper: ElementRef<HTMLInputElement> | 17 | @ViewChild('descriptionWrapper') descriptionWrapper: ElementRef<HTMLInputElement> |
18 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent | 18 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent |
19 | 19 | ||
20 | shortDescription = '' | 20 | aboutHTML: AboutHTML |
21 | descriptionContent: string | 21 | descriptionElement: HTMLDivElement |
22 | |||
23 | html = { | ||
24 | terms: '', | ||
25 | codeOfConduct: '', | ||
26 | moderationInformation: '', | ||
27 | administrator: '', | ||
28 | creationReason: '', | ||
29 | maintenanceLifetime: '', | ||
30 | businessModel: '', | ||
31 | hardwareInformation: '' | ||
32 | } | ||
33 | 22 | ||
34 | languages: string[] = [] | 23 | languages: string[] = [] |
35 | categories: string[] = [] | 24 | categories: string[] = [] |
25 | shortDescription = '' | ||
36 | 26 | ||
37 | initialized = false | 27 | initialized = false |
38 | 28 | ||
@@ -44,8 +34,7 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked { | |||
44 | private viewportScroller: ViewportScroller, | 34 | private viewportScroller: ViewportScroller, |
45 | private route: ActivatedRoute, | 35 | private route: ActivatedRoute, |
46 | private notifier: Notifier, | 36 | private notifier: Notifier, |
47 | private serverService: ServerService, | 37 | private serverService: ServerService |
48 | private instanceService: InstanceService | ||
49 | ) {} | 38 | ) {} |
50 | 39 | ||
51 | get instanceName () { | 40 | get instanceName () { |
@@ -60,8 +49,16 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked { | |||
60 | return this.serverConfig.instance.isNSFW | 49 | return this.serverConfig.instance.isNSFW |
61 | } | 50 | } |
62 | 51 | ||
63 | async ngOnInit () { | 52 | ngOnInit () { |
64 | const { about, languages, categories }: ResolverData = this.route.snapshot.data.instanceData | 53 | const { about, languages, categories, aboutHTML, descriptionElement }: ResolverData = this.route.snapshot.data.instanceData |
54 | |||
55 | this.aboutHTML = aboutHTML | ||
56 | this.descriptionElement = descriptionElement | ||
57 | |||
58 | this.languages = languages | ||
59 | this.categories = categories | ||
60 | |||
61 | this.shortDescription = about.instance.shortDescription | ||
65 | 62 | ||
66 | this.serverConfig = this.serverService.getHTMLConfig() | 63 | this.serverConfig = this.serverService.getHTMLConfig() |
67 | 64 | ||
@@ -73,14 +70,6 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked { | |||
73 | this.contactAdminModal.show(prefill) | 70 | this.contactAdminModal.show(prefill) |
74 | }) | 71 | }) |
75 | 72 | ||
76 | this.languages = languages | ||
77 | this.categories = categories | ||
78 | |||
79 | this.shortDescription = about.instance.shortDescription | ||
80 | this.descriptionContent = about.instance.description | ||
81 | |||
82 | this.html = await this.instanceService.buildHtml(about) | ||
83 | |||
84 | this.initialized = true | 73 | this.initialized = true |
85 | } | 74 | } |
86 | 75 | ||
diff --git a/client/src/app/+about/about-instance/about-instance.resolver.ts b/client/src/app/+about/about-instance/about-instance.resolver.ts index ee0219df0..8818fc582 100644 --- a/client/src/app/+about/about-instance/about-instance.resolver.ts +++ b/client/src/app/+about/about-instance/about-instance.resolver.ts | |||
@@ -2,16 +2,25 @@ import { forkJoin } from 'rxjs' | |||
2 | import { map, switchMap } from 'rxjs/operators' | 2 | import { map, switchMap } from 'rxjs/operators' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { Resolve } from '@angular/router' | 4 | import { Resolve } from '@angular/router' |
5 | import { InstanceService } from '@app/shared/shared-instance' | 5 | import { CustomMarkupService } from '@app/shared/shared-custom-markup' |
6 | import { AboutHTML, InstanceService } from '@app/shared/shared-instance' | ||
6 | import { About } from '@shared/models/server' | 7 | import { About } from '@shared/models/server' |
7 | 8 | ||
8 | export type ResolverData = { about: About, languages: string[], categories: string[] } | 9 | export type ResolverData = { |
10 | about: About | ||
11 | languages: string[] | ||
12 | categories: string[] | ||
13 | aboutHTML: AboutHTML | ||
14 | descriptionElement: HTMLDivElement | ||
15 | } | ||
9 | 16 | ||
10 | @Injectable() | 17 | @Injectable() |
11 | export class AboutInstanceResolver implements Resolve<any> { | 18 | export class AboutInstanceResolver implements Resolve<any> { |
12 | 19 | ||
13 | constructor ( | 20 | constructor ( |
14 | private instanceService: InstanceService | 21 | private instanceService: InstanceService, |
22 | private customMarkupService: CustomMarkupService | ||
23 | |||
15 | ) {} | 24 | ) {} |
16 | 25 | ||
17 | resolve () { | 26 | resolve () { |
@@ -19,9 +28,15 @@ export class AboutInstanceResolver implements Resolve<any> { | |||
19 | .pipe( | 28 | .pipe( |
20 | switchMap(about => { | 29 | switchMap(about => { |
21 | return forkJoin([ | 30 | return forkJoin([ |
31 | Promise.resolve(about), | ||
22 | this.instanceService.buildTranslatedLanguages(about), | 32 | this.instanceService.buildTranslatedLanguages(about), |
23 | this.instanceService.buildTranslatedCategories(about) | 33 | this.instanceService.buildTranslatedCategories(about), |
24 | ]).pipe(map(([ languages, categories ]) => ({ about, languages, categories }) as ResolverData)) | 34 | this.instanceService.buildHtml(about), |
35 | this.customMarkupService.buildElement(about.instance.description) | ||
36 | ]) | ||
37 | }), | ||
38 | map(([ about, languages, categories, aboutHTML, { rootElement } ]) => { | ||
39 | return { about, languages, categories, aboutHTML, descriptionElement: rootElement } as ResolverData | ||
25 | }) | 40 | }) |
26 | ) | 41 | ) |
27 | } | 42 | } |
diff --git a/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts b/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts index 4e802b14d..b2ee2d8f2 100644 --- a/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts +++ b/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts | |||
@@ -6,9 +6,9 @@ import { CustomMarkupService } from './custom-markup.service' | |||
6 | templateUrl: './custom-markup-container.component.html' | 6 | templateUrl: './custom-markup-container.component.html' |
7 | }) | 7 | }) |
8 | export class CustomMarkupContainerComponent implements OnChanges { | 8 | export class CustomMarkupContainerComponent implements OnChanges { |
9 | @ViewChild('contentWrapper') contentWrapper: ElementRef<HTMLInputElement> | 9 | @ViewChild('contentWrapper', { static: true }) contentWrapper: ElementRef<HTMLInputElement> |
10 | 10 | ||
11 | @Input() content: string | 11 | @Input() content: string | HTMLDivElement |
12 | 12 | ||
13 | displayed = false | 13 | displayed = false |
14 | 14 | ||
@@ -17,17 +17,23 @@ export class CustomMarkupContainerComponent implements OnChanges { | |||
17 | ) { } | 17 | ) { } |
18 | 18 | ||
19 | async ngOnChanges () { | 19 | async ngOnChanges () { |
20 | await this.buildElement() | 20 | await this.rebuild() |
21 | } | 21 | } |
22 | 22 | ||
23 | private async buildElement () { | 23 | private async rebuild () { |
24 | if (!this.content) return | 24 | if (this.content instanceof HTMLDivElement) { |
25 | return this.loadElement(this.content) | ||
26 | } | ||
25 | 27 | ||
26 | const { rootElement, componentsLoaded } = await this.customMarkupService.buildElement(this.content) | 28 | const { rootElement, componentsLoaded } = await this.customMarkupService.buildElement(this.content) |
27 | this.contentWrapper.nativeElement.appendChild(rootElement) | ||
28 | |||
29 | await componentsLoaded | 29 | await componentsLoaded |
30 | 30 | ||
31 | return this.loadElement(rootElement) | ||
32 | } | ||
33 | |||
34 | private loadElement (el: HTMLDivElement) { | ||
35 | this.contentWrapper.nativeElement.appendChild(el) | ||
36 | |||
31 | this.displayed = true | 37 | this.displayed = true |
32 | } | 38 | } |
33 | } | 39 | } |
diff --git a/client/src/app/shared/shared-instance/instance.service.ts b/client/src/app/shared/shared-instance/instance.service.ts index 89f47db24..f5b2e05db 100644 --- a/client/src/app/shared/shared-instance/instance.service.ts +++ b/client/src/app/shared/shared-instance/instance.service.ts | |||
@@ -7,6 +7,11 @@ import { peertubeTranslate } from '@shared/core-utils/i18n' | |||
7 | import { About } from '@shared/models' | 7 | import { About } from '@shared/models' |
8 | import { environment } from '../../../environments/environment' | 8 | import { environment } from '../../../environments/environment' |
9 | 9 | ||
10 | export type AboutHTML = Pick<About['instance'], | ||
11 | 'terms' | 'codeOfConduct' | 'moderationInformation' | 'administrator' | 'creationReason' | | ||
12 | 'maintenanceLifetime' | 'businessModel' | 'hardwareInformation' | ||
13 | > | ||
14 | |||
10 | @Injectable() | 15 | @Injectable() |
11 | export class InstanceService { | 16 | export class InstanceService { |
12 | private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config' | 17 | private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config' |
@@ -39,7 +44,7 @@ export class InstanceService { | |||
39 | } | 44 | } |
40 | 45 | ||
41 | async buildHtml (about: About) { | 46 | async buildHtml (about: About) { |
42 | const html = { | 47 | const html: AboutHTML = { |
43 | terms: '', | 48 | terms: '', |
44 | codeOfConduct: '', | 49 | codeOfConduct: '', |
45 | moderationInformation: '', | 50 | moderationInformation: '', |