aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/angular/peertube-template.directive.ts4
-rw-r--r--client/src/app/shared/forms/form-validators/custom-config-validators.service.ts9
-rw-r--r--client/src/app/shared/forms/peertube-checkbox.component.html11
-rw-r--r--client/src/app/shared/forms/peertube-checkbox.component.scss2
-rw-r--r--client/src/app/shared/forms/peertube-checkbox.component.ts24
-rw-r--r--client/src/app/shared/instance/feature-boolean.component.html3
-rw-r--r--client/src/app/shared/instance/feature-boolean.component.scss10
-rw-r--r--client/src/app/shared/instance/feature-boolean.component.ts10
-rw-r--r--client/src/app/shared/instance/instance-features-table.component.html71
-rw-r--r--client/src/app/shared/instance/instance-features-table.component.scss26
-rw-r--r--client/src/app/shared/instance/instance-features-table.component.ts36
-rw-r--r--client/src/app/shared/instance/instance.service.ts46
-rw-r--r--client/src/app/shared/misc/help.component.html28
-rw-r--r--client/src/app/shared/misc/help.component.ts36
-rw-r--r--client/src/app/shared/shared.module.ts9
-rw-r--r--client/src/app/shared/user-subscription/remote-subscribe.component.html22
-rw-r--r--client/src/app/shared/users/user-notification.model.ts9
-rw-r--r--client/src/app/shared/users/user-notifications.component.html10
-rw-r--r--client/src/app/shared/users/user.model.ts33
-rw-r--r--client/src/app/shared/video/videos-selection.component.ts2
20 files changed, 301 insertions, 100 deletions
diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/angular/peertube-template.directive.ts
index a514b6057..e04c25d9a 100644
--- a/client/src/app/shared/angular/peertube-template.directive.ts
+++ b/client/src/app/shared/angular/peertube-template.directive.ts
@@ -3,8 +3,8 @@ import { Directive, Input, TemplateRef } from '@angular/core'
3@Directive({ 3@Directive({
4 selector: '[ptTemplate]' 4 selector: '[ptTemplate]'
5}) 5})
6export class PeerTubeTemplateDirective { 6export class PeerTubeTemplateDirective <T extends string> {
7 @Input('ptTemplate') name: string 7 @Input('ptTemplate') name: T
8 8
9 constructor (public template: TemplateRef<any>) { 9 constructor (public template: TemplateRef<any>) {
10 // empty 10 // empty
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
index 882e39453..767e3f026 100644
--- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
@@ -13,6 +13,7 @@ export class CustomConfigValidatorsService {
13 readonly SIGNUP_LIMIT: BuildFormValidator 13 readonly SIGNUP_LIMIT: BuildFormValidator
14 readonly ADMIN_EMAIL: BuildFormValidator 14 readonly ADMIN_EMAIL: BuildFormValidator
15 readonly TRANSCODING_THREADS: BuildFormValidator 15 readonly TRANSCODING_THREADS: BuildFormValidator
16 readonly INDEX_URL: BuildFormValidator
16 17
17 constructor (private i18n: I18n) { 18 constructor (private i18n: I18n) {
18 this.INSTANCE_NAME = { 19 this.INSTANCE_NAME = {
@@ -78,5 +79,13 @@ export class CustomConfigValidatorsService {
78 'min': this.i18n('Transcoding threads must be greater or equal to 0.') 79 'min': this.i18n('Transcoding threads must be greater or equal to 0.')
79 } 80 }
80 } 81 }
82
83 this.INDEX_URL = {
84 VALIDATORS: [ Validators.required, Validators.pattern(/^https:\/\//) ],
85 MESSAGES: {
86 'required': this.i18n('Index URL is required.'),
87 'pattern': this.i18n('Index URL should be a URL')
88 }
89 }
81 } 90 }
82} 91}
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/forms/peertube-checkbox.component.html
index 571a1a673..f1e3bf0bf 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.html
+++ b/client/src/app/shared/forms/peertube-checkbox.component.html
@@ -3,8 +3,15 @@
3 <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> 3 <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" />
4 <span role="checkbox" [attr.aria-checked]="checked"></span> 4 <span role="checkbox" [attr.aria-checked]="checked"></span>
5 <span *ngIf="labelText">{{ labelText }}</span> 5 <span *ngIf="labelText">{{ labelText }}</span>
6 <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> 6
7 <span *ngIf="labelTemplate">
8 <ng-container *ngTemplateOutlet="labelTemplate"></ng-container>
9 </span>
7 </label> 10 </label>
8 11
9 <my-help *ngIf="helpHtml" [tooltipPlacement]="helpPlacement" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> 12 <my-help *ngIf="helpTemplate" [tooltipPlacement]="helpPlacement" helpType="custom">
13 <ng-template ptTemplate="customHtml">
14 <ng-template *ngTemplateOutlet="helpTemplate"></ng-template>
15 </ng-template>
16 </my-help>
10</div> 17</div>
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/forms/peertube-checkbox.component.scss
index 84ea788af..51f98b0bc 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.scss
+++ b/client/src/app/shared/forms/peertube-checkbox.component.scss
@@ -7,7 +7,7 @@
7 .form-group-checkbox { 7 .form-group-checkbox {
8 display: flex; 8 display: flex;
9 9
10 span { 10 .label-text {
11 font-weight: $font-regular; 11 font-weight: $font-regular;
12 margin: 0; 12 margin: 0;
13 } 13 }
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/forms/peertube-checkbox.component.ts
index a4b72aa37..3b8f39ed0 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.ts
+++ b/client/src/app/shared/forms/peertube-checkbox.component.ts
@@ -1,5 +1,6 @@
1import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core' 1import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
3 4
4@Component({ 5@Component({
5 selector: 'my-peertube-checkbox', 6 selector: 'my-peertube-checkbox',
@@ -13,20 +14,35 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
13 } 14 }
14 ] 15 ]
15}) 16})
16export class PeertubeCheckboxComponent implements ControlValueAccessor { 17export class PeertubeCheckboxComponent implements ControlValueAccessor, AfterContentInit {
17 @Input() checked = false 18 @Input() checked = false
18 @Input() inputName: string 19 @Input() inputName: string
19 @Input() labelText: string 20 @Input() labelText: string
20 @Input() labelHtml: string
21 @Input() helpHtml: string
22 @Input() helpPlacement = 'top' 21 @Input() helpPlacement = 'top'
23 @Input() disabled = false 22 @Input() disabled = false
24 23
24 @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'label' | 'help'>>
25
25 // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836 26 // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836
26 @Input() onPushWorkaround = false 27 @Input() onPushWorkaround = false
27 28
29 labelTemplate: TemplateRef<any>
30 helpTemplate: TemplateRef<any>
31
28 constructor (private cdr: ChangeDetectorRef) { } 32 constructor (private cdr: ChangeDetectorRef) { }
29 33
34 ngAfterContentInit () {
35 {
36 const t = this.templates.find(t => t.name === 'label')
37 if (t) this.labelTemplate = t.template
38 }
39
40 {
41 const t = this.templates.find(t => t.name === 'help')
42 if (t) this.helpTemplate = t.template
43 }
44 }
45
30 propagateChange = (_: any) => { /* empty */ } 46 propagateChange = (_: any) => { /* empty */ }
31 47
32 writeValue (checked: boolean) { 48 writeValue (checked: boolean) {
diff --git a/client/src/app/shared/instance/feature-boolean.component.html b/client/src/app/shared/instance/feature-boolean.component.html
new file mode 100644
index 000000000..ac208fc13
--- /dev/null
+++ b/client/src/app/shared/instance/feature-boolean.component.html
@@ -0,0 +1,3 @@
1<span *ngIf="value === true" class="glyphicon glyphicon-ok"></span>
2<span *ngIf="value === false" class="glyphicon glyphicon-remove"></span>
3
diff --git a/client/src/app/shared/instance/feature-boolean.component.scss b/client/src/app/shared/instance/feature-boolean.component.scss
new file mode 100644
index 000000000..56d08af06
--- /dev/null
+++ b/client/src/app/shared/instance/feature-boolean.component.scss
@@ -0,0 +1,10 @@
1@import '_variables';
2@import '_mixins';
3
4.glyphicon-ok {
5 color: $green;
6}
7
8.glyphicon-remove {
9 color: $red;
10}
diff --git a/client/src/app/shared/instance/feature-boolean.component.ts b/client/src/app/shared/instance/feature-boolean.component.ts
new file mode 100644
index 000000000..d02d513d6
--- /dev/null
+++ b/client/src/app/shared/instance/feature-boolean.component.ts
@@ -0,0 +1,10 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-feature-boolean',
5 templateUrl: './feature-boolean.component.html',
6 styleUrls: [ './feature-boolean.component.scss' ]
7})
8export class FeatureBooleanComponent {
9 @Input() value: boolean
10}
diff --git a/client/src/app/shared/instance/instance-features-table.component.html b/client/src/app/shared/instance/instance-features-table.component.html
index 2987bd00e..d1cb8fcbe 100644
--- a/client/src/app/shared/instance/instance-features-table.component.html
+++ b/client/src/app/shared/instance/instance-features-table.component.html
@@ -1,28 +1,53 @@
1<div class="feature-table"> 1<div class="feature-table">
2 2
3 <table class="table"> 3 <table class="table" *ngIf="config">
4 <tr> 4 <tr>
5 <td i18n class="label">Default NSFW/sensitive videos policy (can be redefined by the users)</td> 5 <td i18n class="label">
6 <div>Default NSFW/sensitive videos policy</div>
7 <div class="more-info">can be redefined by the users</div>
8 </td>
6 9
7 <td class="value">{{ buildNSFWLabel() }}</td> 10 <td class="value">{{ buildNSFWLabel() }}</td>
8 </tr> 11 </tr>
9 12
10 <tr *ngFor="let feature of features"> 13 <tr>
11 <td class="label">{{ feature.label }}</td> 14 <td i18n class="label">User registration allowed</td>
12 <td> 15 <td>
13 <span *ngIf="feature.value === true" class="glyphicon glyphicon-ok"></span> 16 <my-feature-boolean [value]="config.signup.allowed"></my-feature-boolean>
14 <span *ngIf="feature.value === false" class="glyphicon glyphicon-remove"></span>
15 </td> 17 </td>
16 </tr> 18 </tr>
17 19
18 <tr> 20 <tr>
19 <td i18n class="label">Video quota</td> 21 <td i18n class="label" colspan="2">Video uploads</td>
22 </tr>
23
24 <tr>
25 <td i18n class="sub-label">Transcoding in multiple resolutions</td>
26 <td>
27 <my-feature-boolean [value]="config.transcoding.enabledResolutions.length !== 0"></my-feature-boolean>
28 </td>
29 </tr>
30
31 <tr>
32 <td i18n class="sub-label">Video uploads</td>
33 <td>
34 <span *ngIf="config.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span>
35 <span *ngIf="!config.autoBlacklist.videos.ofUsers.enabled">Automatically published</span>
36 </td>
37 </tr>
38
39 <tr>
40 <td i18n class="sub-label">Video quota</td>
20 41
21 <td class="value"> 42 <td class="value">
22 <ng-container *ngIf="initialUserVideoQuota !== -1"> 43 <ng-container *ngIf="initialUserVideoQuota !== -1">
23 {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container> 44 {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container>
24 45
25 <my-help tooltipPlacement="auto" helpType="custom" [customHtml]="quotaHelpIndication"></my-help> 46 <my-help tooltipPlacement="auto" helpType="custom">
47 <ng-template ptTemplate="customHtml">
48 <div [innerHTML]="quotaHelpIndication"></div>
49 </ng-template>
50 </my-help>
26 </ng-container> 51 </ng-container>
27 52
28 <ng-container i18n *ngIf="initialUserVideoQuota === -1"> 53 <ng-container i18n *ngIf="initialUserVideoQuota === -1">
@@ -30,5 +55,35 @@
30 </ng-container> 55 </ng-container>
31 </td> 56 </td>
32 </tr> 57 </tr>
58
59 <tr>
60 <td i18n class="label" colspan="2">Import</td>
61 </tr>
62
63 <tr>
64 <td i18n class="sub-label">HTTP import (YouTube, Vimeo, direct URL...)</td>
65 <td>
66 <my-feature-boolean [value]="config.import.videos.http.enabled"></my-feature-boolean>
67 </td>
68 </tr>
69
70 <tr>
71 <td i18n class="sub-label">Torrent import</td>
72 <td>
73 <my-feature-boolean [value]="config.import.videos.torrent.enabled"></my-feature-boolean>
74 </td>
75 </tr>
76
77
78 <tr>
79 <td i18n class="label" colspan="2">Player</td>
80 </tr>
81
82 <tr>
83 <td i18n class="sub-label">P2P enabled</td>
84 <td>
85 <my-feature-boolean [value]="config.tracker.enabled"></my-feature-boolean>
86 </td>
87 </tr>
33 </table> 88 </table>
34</div> 89</div>
diff --git a/client/src/app/shared/instance/instance-features-table.component.scss b/client/src/app/shared/instance/instance-features-table.component.scss
index f9bec038d..67f2b6c84 100644
--- a/client/src/app/shared/instance/instance-features-table.component.scss
+++ b/client/src/app/shared/instance/instance-features-table.component.scss
@@ -5,16 +5,28 @@ table {
5 font-size: 14px; 5 font-size: 14px;
6 color: var(--mainForegroundColor); 6 color: var(--mainForegroundColor);
7 7
8 .label { 8 .label,
9 font-weight: $font-semibold; 9 .sub-label {
10 min-width: 330px; 10 min-width: 330px;
11 }
12 11
13 .glyphicon-ok { 12 &.label {
14 color: $green; 13 font-weight: $font-semibold;
14 }
15
16 &.sub-label {
17 padding-left: 30px;
18 }
19
20 .more-info {
21 font-style: italic;
22 font-weight: initial;
23 font-size: 14px
24 }
15 } 25 }
16 26
17 .glyphicon-remove { 27 td {
18 color: $red; 28 vertical-align: middle;
19 } 29 }
20} 30}
31
32
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/instance/instance-features-table.component.ts
index a53082a93..46df4d0b2 100644
--- a/client/src/app/shared/instance/instance-features-table.component.ts
+++ b/client/src/app/shared/instance/instance-features-table.component.ts
@@ -1,6 +1,7 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { ServerService } from '@app/core' 2import { ServerService } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { ServerConfig } from '@shared/models'
4 5
5@Component({ 6@Component({
6 selector: 'my-instance-features-table', 7 selector: 'my-instance-features-table',
@@ -8,8 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
8 styleUrls: [ './instance-features-table.component.scss' ] 9 styleUrls: [ './instance-features-table.component.scss' ]
9}) 10})
10export class InstanceFeaturesTableComponent implements OnInit { 11export class InstanceFeaturesTableComponent implements OnInit {
11 features: { label: string, value?: boolean }[] = []
12 quotaHelpIndication = '' 12 quotaHelpIndication = ''
13 config: ServerConfig
13 14
14 constructor ( 15 constructor (
15 private i18n: I18n, 16 private i18n: I18n,
@@ -28,7 +29,7 @@ export class InstanceFeaturesTableComponent implements OnInit {
28 ngOnInit () { 29 ngOnInit () {
29 this.serverService.configLoaded 30 this.serverService.configLoaded
30 .subscribe(() => { 31 .subscribe(() => {
31 this.buildFeatures() 32 this.config = this.serverService.getConfig()
32 this.buildQuotaHelpIndication() 33 this.buildQuotaHelpIndication()
33 }) 34 })
34 } 35 }
@@ -41,37 +42,6 @@ export class InstanceFeaturesTableComponent implements OnInit {
41 if (policy === 'display') return this.i18n('Displayed') 42 if (policy === 'display') return this.i18n('Displayed')
42 } 43 }
43 44
44 private buildFeatures () {
45 const config = this.serverService.getConfig()
46
47 this.features = [
48 {
49 label: this.i18n('User registration allowed'),
50 value: config.signup.allowed
51 },
52 {
53 label: this.i18n('Video uploads require manual validation by moderators'),
54 value: config.autoBlacklist.videos.ofUsers.enabled
55 },
56 {
57 label: this.i18n('Transcode your videos in multiple resolutions'),
58 value: config.transcoding.enabledResolutions.length !== 0
59 },
60 {
61 label: this.i18n('HTTP import (YouTube, Vimeo, direct URL...)'),
62 value: config.import.videos.http.enabled
63 },
64 {
65 label: this.i18n('Torrent import'),
66 value: config.import.videos.torrent.enabled
67 },
68 {
69 label: this.i18n('P2P enabled'),
70 value: config.tracker.enabled
71 }
72 ]
73 }
74
75 private getApproximateTime (seconds: number) { 45 private getApproximateTime (seconds: number) {
76 const hours = Math.floor(seconds / 3600) 46 const hours = Math.floor(seconds / 3600)
77 let pluralSuffix = '' 47 let pluralSuffix = ''
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts
index d0c96941d..44b413fa4 100644
--- a/client/src/app/shared/instance/instance.service.ts
+++ b/client/src/app/shared/instance/instance.service.ts
@@ -4,6 +4,9 @@ import { Injectable } from '@angular/core'
4import { environment } from '../../../environments/environment' 4import { environment } from '../../../environments/environment'
5import { RestExtractor, RestService } from '../rest' 5import { RestExtractor, RestService } from '../rest'
6import { About } from '../../../../../shared/models/server' 6import { About } from '../../../../../shared/models/server'
7import { MarkdownService } from '@app/shared/renderer'
8import { peertubeTranslate } from '@shared/models'
9import { ServerService } from '@app/core'
7 10
8@Injectable() 11@Injectable()
9export class InstanceService { 12export class InstanceService {
@@ -13,7 +16,9 @@ export class InstanceService {
13 constructor ( 16 constructor (
14 private authHttp: HttpClient, 17 private authHttp: HttpClient,
15 private restService: RestService, 18 private restService: RestService,
16 private restExtractor: RestExtractor 19 private restExtractor: RestExtractor,
20 private markdownService: MarkdownService,
21 private serverService: ServerService
17 ) { 22 ) {
18 } 23 }
19 24
@@ -34,4 +39,43 @@ export class InstanceService {
34 .pipe(catchError(res => this.restExtractor.handleError(res))) 39 .pipe(catchError(res => this.restExtractor.handleError(res)))
35 40
36 } 41 }
42
43 async buildHtml (about: About) {
44 const html = {
45 description: '',
46 terms: '',
47 codeOfConduct: '',
48 moderationInformation: '',
49 administrator: '',
50 hardwareInformation: ''
51 }
52
53 for (const key of Object.keys(html)) {
54 html[ key ] = await this.markdownService.textMarkdownToHTML(about.instance[ key ])
55 }
56
57 return html
58 }
59
60 buildTranslatedLanguages (about: About, translations: any) {
61 const languagesArray = this.serverService.getVideoLanguages()
62
63 return about.instance.languages
64 .map(l => {
65 const languageObj = languagesArray.find(la => la.id === l)
66
67 return peertubeTranslate(languageObj.label, translations)
68 })
69 }
70
71 buildTranslatedCategories (about: About, translations: any) {
72 const categoriesArray = this.serverService.getVideoCategories()
73
74 return about.instance.categories
75 .map(c => {
76 const categoryObj = categoriesArray.find(ca => ca.id === c)
77
78 return peertubeTranslate(categoryObj.label, translations)
79 })
80 }
37} 81}
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html
index e31eef06a..9a6d3e48e 100644
--- a/client/src/app/shared/misc/help.component.html
+++ b/client/src/app/shared/misc/help.component.html
@@ -1,15 +1,25 @@
1<ng-template #tooltipTemplate> 1<ng-template #tooltipTemplate>
2 <ng-template [ngIf]="preHtml"> 2 <p *ngIf="preHtmlTemplate">
3 <p [innerHTML]="preHtml"></p> 3 <ng-template *ngTemplateOutlet="preHtmlTemplate"></ng-template>
4 <br /> 4 </p>
5 </ng-template>
6 5
7 <p [innerHTML]="mainHtml"></p> 6 <ng-container *ngIf="preHtmlTemplate && (customHtmlTemplate || mainHtml || postHtmlTemplate)">
7 <br /><br />
8 </ng-container>
8 9
9 <ng-template [ngIf]="postHtml"> 10 <p *ngIf="customHtmlTemplate">
10 <br /> 11 <ng-template *ngTemplateOutlet="customHtmlTemplate"></ng-template>
11 <p [innerHTML]="postHtml"></p> 12 </p>
12 </ng-template> 13
14 <p *ngIf="mainHtml" [innerHTML]="mainHtml"></p>
15
16 <ng-container *ngIf="(customHtmlTemplate || mainHtml) && postHtmlTemplate">
17 <br /><br />
18 </ng-container>
19
20 <p *ngIf="postHtmlTemplate">
21 <ng-template *ngTemplateOutlet="postHtmlTemplate"></ng-template>
22 </p>
13</ng-template> 23</ng-template>
14 24
15<span 25<span
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/misc/help.component.ts
index f3426f70f..18ba8ad5e 100644
--- a/client/src/app/shared/misc/help.component.ts
+++ b/client/src/app/shared/misc/help.component.ts
@@ -1,6 +1,7 @@
1import { Component, Input, OnChanges, OnInit } from '@angular/core' 1import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core'
2import { I18n } from '@ngx-translate/i18n-polyfill' 2import { I18n } from '@ngx-translate/i18n-polyfill'
3import { MarkdownService } from '@app/shared/renderer' 3import { MarkdownService } from '@app/shared/renderer'
4import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
4 5
5@Component({ 6@Component({
6 selector: 'my-help', 7 selector: 'my-help',
@@ -8,22 +9,42 @@ import { MarkdownService } from '@app/shared/renderer'
8 templateUrl: './help.component.html' 9 templateUrl: './help.component.html'
9}) 10})
10 11
11export class HelpComponent implements OnInit, OnChanges { 12export class HelpComponent implements OnInit, OnChanges, AfterContentInit {
12 @Input() preHtml = ''
13 @Input() postHtml = ''
14 @Input() customHtml = ''
15 @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' 13 @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom'
16 @Input() tooltipPlacement = 'right' 14 @Input() tooltipPlacement = 'right'
17 15
16 @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'preHtml' | 'customHtml' | 'postHtml'>>
17
18 isPopoverOpened = false 18 isPopoverOpened = false
19 mainHtml = '' 19 mainHtml = ''
20 20
21 preHtmlTemplate: TemplateRef<any>
22 customHtmlTemplate: TemplateRef<any>
23 postHtmlTemplate: TemplateRef<any>
24
21 constructor (private i18n: I18n) { } 25 constructor (private i18n: I18n) { }
22 26
23 ngOnInit () { 27 ngOnInit () {
24 this.init() 28 this.init()
25 } 29 }
26 30
31 ngAfterContentInit () {
32 {
33 const t = this.templates.find(t => t.name === 'preHtml')
34 if (t) this.preHtmlTemplate = t.template
35 }
36
37 {
38 const t = this.templates.find(t => t.name === 'customHtml')
39 if (t) this.customHtmlTemplate = t.template
40 }
41
42 {
43 const t = this.templates.find(t => t.name === 'postHtml')
44 if (t) this.postHtmlTemplate = t.template
45 }
46 }
47
27 ngOnChanges () { 48 ngOnChanges () {
28 this.init() 49 this.init()
29 } 50 }
@@ -37,11 +58,6 @@ export class HelpComponent implements OnInit, OnChanges {
37 } 58 }
38 59
39 private init () { 60 private init () {
40 if (this.helpType === 'custom') {
41 this.mainHtml = this.customHtml
42 return
43 }
44
45 if (this.helpType === 'markdownText') { 61 if (this.helpType === 'markdownText') {
46 this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES) 62 this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES)
47 return 63 return
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index eb57a2fff..65e0f21a4 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -6,10 +6,8 @@ import { RouterModule } from '@angular/router'
6import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' 6import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
7import { HelpComponent } from '@app/shared/misc/help.component' 7import { HelpComponent } from '@app/shared/misc/help.component'
8import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' 8import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
9
10import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' 9import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
11import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' 10import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
12
13import { AUTH_INTERCEPTOR_PROVIDER } from './auth' 11import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
14import { ButtonComponent } from './buttons/button.component' 12import { ButtonComponent } from './buttons/button.component'
15import { DeleteButtonComponent } from './buttons/delete-button.component' 13import { DeleteButtonComponent } from './buttons/delete-button.component'
@@ -93,6 +91,8 @@ import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.
93import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' 91import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
94import { ClipboardModule } from 'ngx-clipboard' 92import { ClipboardModule } from 'ngx-clipboard'
95import { FollowService } from '@app/shared/instance/follow.service' 93import { FollowService } from '@app/shared/instance/follow.service'
94import { MultiSelectModule } from 'primeng/multiselect'
95import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component'
96 96
97@NgModule({ 97@NgModule({
98 imports: [ 98 imports: [
@@ -113,7 +113,8 @@ import { FollowService } from '@app/shared/instance/follow.service'
113 113
114 PrimeSharedModule, 114 PrimeSharedModule,
115 InputMaskModule, 115 InputMaskModule,
116 NgPipesModule 116 NgPipesModule,
117 MultiSelectModule
117 ], 118 ],
118 119
119 declarations: [ 120 declarations: [
@@ -156,6 +157,7 @@ import { FollowService } from '@app/shared/instance/follow.service'
156 SubscribeButtonComponent, 157 SubscribeButtonComponent,
157 RemoteSubscribeComponent, 158 RemoteSubscribeComponent,
158 InstanceFeaturesTableComponent, 159 InstanceFeaturesTableComponent,
160 FeatureBooleanComponent,
159 UserBanModalComponent, 161 UserBanModalComponent,
160 UserModerationDropdownComponent, 162 UserModerationDropdownComponent,
161 TopMenuDropdownComponent, 163 TopMenuDropdownComponent,
@@ -186,6 +188,7 @@ import { FollowService } from '@app/shared/instance/follow.service'
186 InputMaskModule, 188 InputMaskModule,
187 BytesPipe, 189 BytesPipe,
188 KeysPipe, 190 KeysPipe,
191 MultiSelectModule,
189 192
190 LoaderComponent, 193 LoaderComponent,
191 SmallLoaderComponent, 194 SmallLoaderComponent,
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/user-subscription/remote-subscribe.component.html
index ec3636b3e..59ee1cb04 100644
--- a/client/src/app/shared/user-subscription/remote-subscribe.component.html
+++ b/client/src/app/shared/user-subscription/remote-subscribe.component.html
@@ -12,13 +12,21 @@
12 <span *ngIf="interact">Remote interact</span> 12 <span *ngIf="interact">Remote interact</span>
13 </button> 13 </button>
14 14
15 <my-help *ngIf="!interact && showHelp" 15 <my-help *ngIf="!interact && showHelp">
16 helpType="custom" 16 <ng-template ptTemplate="customHtml">
17 i18n-customHtml customHtml="You can subscribe to the channel via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there."> 17 <ng-container i18n>
18 You can subscribe to the channel via any ActivityPub-capable fediverse instance.<br /><br />
19 For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there.
20 </ng-container>
21 </ng-template>
18 </my-help> 22 </my-help>
19 23
20 <my-help *ngIf="showHelp && interact" 24 <my-help *ngIf="showHelp && interact">
21 helpType="custom" 25 <ng-template ptTemplate="customHtml">
22 i18n-customHtml customHtml="You can interact with this via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there."> 26 <ng-container i18n>
27 You can interact with this via any ActivityPub-capable fediverse instance.<br /><br />
28 For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there.
29 </ng-container>
30 </ng-template>
23 </my-help> 31 </my-help>
24</form> \ No newline at end of file 32</form>
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts
index 06eace71c..b4ac075c5 100644
--- a/client/src/app/shared/users/user-notification.model.ts
+++ b/client/src/app/shared/users/user-notification.model.ts
@@ -42,9 +42,10 @@ export class UserNotification implements UserNotificationServer {
42 state: FollowState 42 state: FollowState
43 follower: ActorInfo & { avatarUrl?: string } 43 follower: ActorInfo & { avatarUrl?: string }
44 following: { 44 following: {
45 type: 'account' | 'channel' 45 type: 'account' | 'channel' | 'instance'
46 name: string 46 name: string
47 displayName: string 47 displayName: string
48 host: string
48 } 49 }
49 } 50 }
50 51
@@ -112,7 +113,7 @@ export class UserNotification implements UserNotificationServer {
112 113
113 case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: 114 case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
114 this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' 115 this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list'
115 this.videoUrl = this.buildVideoUrl(this.video) 116 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
116 break 117 break
117 118
118 case UserNotificationType.BLACKLIST_ON_MY_VIDEO: 119 case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
@@ -146,6 +147,10 @@ export class UserNotification implements UserNotificationServer {
146 case UserNotificationType.NEW_INSTANCE_FOLLOWER: 147 case UserNotificationType.NEW_INSTANCE_FOLLOWER:
147 this.instanceFollowUrl = '/admin/follows/followers-list' 148 this.instanceFollowUrl = '/admin/follows/followers-list'
148 break 149 break
150
151 case UserNotificationType.AUTO_INSTANCE_FOLLOWING:
152 this.instanceFollowUrl = '/admin/follows/following-list'
153 break
149 } 154 }
150 } catch (err) { 155 } catch (err) {
151 this.type = null 156 this.type = null
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html
index 292813426..0702d3b5e 100644
--- a/client/src/app/shared/users/user-notifications.component.html
+++ b/client/src/app/shared/users/user-notifications.component.html
@@ -40,7 +40,7 @@
40 <my-global-icon iconName="no"></my-global-icon> 40 <my-global-icon iconName="no"></my-global-icon>
41 41
42 <div class="message"> 42 <div class="message">
43 The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a> 43 The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a>
44 </div> 44 </div>
45 </ng-container> 45 </ng-container>
46 46
@@ -111,6 +111,14 @@
111 <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container> 111 <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container>
112 </div> 112 </div>
113 </ng-container> 113 </ng-container>
114
115 <ng-container i18n *ngSwitchCase="UserNotificationType.AUTO_INSTANCE_FOLLOWING">
116 <my-global-icon iconName="users"></my-global-icon>
117
118 <div class="message">
119 Your instance automatically followed <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">{{ notification.actorFollow.following.host }}</a>
120 </div>
121 </ng-container>
114 </ng-container> 122 </ng-container>
115 123
116 <div class="from-date">{{ notification.createdAt | myFromNow }}</div> 124 <div class="from-date">{{ notification.createdAt | myFromNow }}</div>
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 53809f82c..656b73dd2 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -9,31 +9,38 @@ export class User implements UserServerModel {
9 username: string 9 username: string
10 email: string 10 email: string
11 pendingEmail: string | null 11 pendingEmail: string | null
12
12 emailVerified: boolean 13 emailVerified: boolean
13 nsfwPolicy: NSFWPolicyType 14 nsfwPolicy: NSFWPolicyType
14 15
15 role: UserRole 16 adminFlags?: UserAdminFlag
16 roleLabel: string
17 17
18 webTorrentEnabled: boolean
19 autoPlayVideo: boolean 18 autoPlayVideo: boolean
19 webTorrentEnabled: boolean
20 videosHistoryEnabled: boolean 20 videosHistoryEnabled: boolean
21 videoLanguages: string[] 21 videoLanguages: string[]
22 22
23 role: UserRole
24 roleLabel: string
25
23 videoQuota: number 26 videoQuota: number
24 videoQuotaDaily: number 27 videoQuotaDaily: number
25 account: Account 28 videoQuotaUsed?: number
26 videoChannels: VideoChannel[] 29 videoQuotaUsedDaily?: number
27 createdAt: Date
28 30
29 theme: string 31 theme: string
30 32
31 adminFlags?: UserAdminFlag 33 account: Account
34 notificationSettings?: UserNotificationSetting
35 videoChannels?: VideoChannel[]
32 36
33 blocked: boolean 37 blocked: boolean
34 blockedReason?: string 38 blockedReason?: string
35 39
36 notificationSettings?: UserNotificationSetting 40 noInstanceConfigWarningModal: boolean
41 noWelcomeModal: boolean
42
43 createdAt: Date
37 44
38 constructor (hash: Partial<UserServerModel>) { 45 constructor (hash: Partial<UserServerModel>) {
39 this.id = hash.id 46 this.id = hash.id
@@ -43,13 +50,16 @@ export class User implements UserServerModel {
43 this.role = hash.role 50 this.role = hash.role
44 51
45 this.videoChannels = hash.videoChannels 52 this.videoChannels = hash.videoChannels
53
46 this.videoQuota = hash.videoQuota 54 this.videoQuota = hash.videoQuota
47 this.videoQuotaDaily = hash.videoQuotaDaily 55 this.videoQuotaDaily = hash.videoQuotaDaily
56 this.videoQuotaUsed = hash.videoQuotaUsed
57 this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
58
48 this.nsfwPolicy = hash.nsfwPolicy 59 this.nsfwPolicy = hash.nsfwPolicy
49 this.webTorrentEnabled = hash.webTorrentEnabled 60 this.webTorrentEnabled = hash.webTorrentEnabled
50 this.videosHistoryEnabled = hash.videosHistoryEnabled 61 this.videosHistoryEnabled = hash.videosHistoryEnabled
51 this.autoPlayVideo = hash.autoPlayVideo 62 this.autoPlayVideo = hash.autoPlayVideo
52 this.createdAt = hash.createdAt
53 63
54 this.theme = hash.theme 64 this.theme = hash.theme
55 65
@@ -58,8 +68,13 @@ export class User implements UserServerModel {
58 this.blocked = hash.blocked 68 this.blocked = hash.blocked
59 this.blockedReason = hash.blockedReason 69 this.blockedReason = hash.blockedReason
60 70
71 this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal
72 this.noWelcomeModal = hash.noWelcomeModal
73
61 this.notificationSettings = hash.notificationSettings 74 this.notificationSettings = hash.notificationSettings
62 75
76 this.createdAt = hash.createdAt
77
63 if (hash.account !== undefined) { 78 if (hash.account !== undefined) {
64 this.account = new Account(hash.account) 79 this.account = new Account(hash.account)
65 } 80 }
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts
index 994e0fa1e..064420056 100644
--- a/client/src/app/shared/video/videos-selection.component.ts
+++ b/client/src/app/shared/video/videos-selection.component.ts
@@ -35,7 +35,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
35 @Input() titlePage: string 35 @Input() titlePage: string
36 @Input() miniatureDisplayOptions: MiniatureDisplayOptions 36 @Input() miniatureDisplayOptions: MiniatureDisplayOptions
37 @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> 37 @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>>
38 @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective> 38 @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'rowButtons' | 'globalButtons'>>
39 39
40 @Output() selectionChange = new EventEmitter<SelectionType>() 40 @Output() selectionChange = new EventEmitter<SelectionType>()
41 @Output() videosModelChange = new EventEmitter<Video[]>() 41 @Output() videosModelChange = new EventEmitter<Video[]>()