diff options
Diffstat (limited to 'client/src')
52 files changed, 1373 insertions, 310 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 7c27ec760..9499bbe4e 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -1,27 +1,92 @@ | |||
1 | <div class="row"> | 1 | <div class="row"> |
2 | <div class="col-md-12 col-xl-6"> | 2 | <div class="col-md-12 col-xl-6"> |
3 | |||
3 | <div class="about-instance-title"> | 4 | <div class="about-instance-title"> |
4 | <div i18n>About {{ instanceName }} instance</div> | 5 | <div i18n class="title">About {{ instanceName }} instance</div> |
5 | 6 | ||
6 | <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div> | 7 | <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div> |
7 | </div> | 8 | </div> |
8 | 9 | ||
10 | <div class="block instance-badges"> | ||
11 | <span *ngFor="let category of categories" class="badge badge-primary category">{{ category }}</span> | ||
12 | |||
13 | <span *ngFor="let language of languages" class="badge badge-secondary language">{{ language }}</span> | ||
14 | </div> | ||
15 | |||
9 | <div class="short-description"> | 16 | <div class="short-description"> |
10 | <div>{{ shortDescription }}</div> | 17 | <div class="block short-description">{{ shortDescription }}</div> |
18 | |||
19 | <div *ngIf="isNSFW" class="block dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div> | ||
20 | </div> | ||
21 | |||
22 | <div class="middle-title" *ngIf="html.administrator || maintenanceLifetime || businessModel"> | ||
23 | Administrators & sustainability | ||
24 | </div> | ||
25 | |||
26 | <div class="block administrator" *ngIf="html.administrator"> | ||
27 | <div i18n class="section-title">Who we are</div> | ||
11 | 28 | ||
12 | <div *ngIf="isNSFW" class="dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div> | 29 | <div [innerHTML]="html.administrator"></div> |
13 | </div> | 30 | </div> |
14 | 31 | ||
15 | <div class="description"> | 32 | <div class="block creation-reason" *ngIf="creationReason"> |
33 | <div i18n class="section-title">Why we created this instance</div> | ||
34 | |||
35 | <p>{{ creationReason }}</p> | ||
36 | </div> | ||
37 | |||
38 | <div class="block maintenance-lifetime" *ngIf="maintenanceLifetime"> | ||
39 | <div i18n class="section-title">How long we plan to maintain this instance</div> | ||
40 | |||
41 | <p>{{ maintenanceLifetime }}</p> | ||
42 | </div> | ||
43 | |||
44 | <div class="block business-model" *ngIf="businessModel"> | ||
45 | <div i18n class="section-title">How we will pay this instance</div> | ||
46 | |||
47 | <p>{{ businessModel }}</p> | ||
48 | </div> | ||
49 | |||
50 | <div class="middle-title" *ngIf="html.description"> | ||
51 | Information | ||
52 | </div> | ||
53 | |||
54 | <div class="block description"> | ||
16 | <div i18n class="section-title">Description</div> | 55 | <div i18n class="section-title">Description</div> |
17 | 56 | ||
18 | <div [innerHTML]="descriptionHTML"></div> | 57 | <div [innerHTML]="html.description"></div> |
58 | </div> | ||
59 | |||
60 | <div class="middle-title" *ngIf="html.moderationInformation || html.codeOfConduct || html.terms"> | ||
61 | Moderation | ||
62 | </div> | ||
63 | |||
64 | <div class="block moderation-information" *ngIf="html.moderationInformation"> | ||
65 | <div i18n class="section-title">Moderation information</div> | ||
66 | |||
67 | <div [innerHTML]="html.moderationInformation"></div> | ||
19 | </div> | 68 | </div> |
20 | 69 | ||
21 | <div class="terms" id="terms-section"> | 70 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> |
71 | <div i18n class="section-title">Code of conduct</div> | ||
72 | |||
73 | <div [innerHTML]="html.codeOfConduct"></div> | ||
74 | </div> | ||
75 | |||
76 | <div class="block terms"> | ||
22 | <div i18n class="section-title">Terms</div> | 77 | <div i18n class="section-title">Terms</div> |
23 | 78 | ||
24 | <div [innerHTML]="termsHTML"></div> | 79 | <div [innerHTML]="html.terms"></div> |
80 | </div> | ||
81 | |||
82 | <div class="middle-title" *ngIf="html.hardwareInformation"> | ||
83 | Other information | ||
84 | </div> | ||
85 | |||
86 | <div class="block hardware-information"> | ||
87 | <div i18n class="section-title">Hardware information</div> | ||
88 | |||
89 | <div [innerHTML]="html.hardwareInformation"></div> | ||
25 | </div> | 90 | </div> |
26 | </div> | 91 | </div> |
27 | 92 | ||
diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss index 0296ae8e9..909ae5c21 100644 --- a/client/src/app/+about/about-instance/about-instance.component.scss +++ b/client/src/app/+about/about-instance/about-instance.component.scss | |||
@@ -5,13 +5,12 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | justify-content: space-between; | 6 | justify-content: space-between; |
7 | 7 | ||
8 | & > div { | 8 | .title { |
9 | font-size: 20px; | 9 | font-size: 20px; |
10 | font-weight: bold; | 10 | font-weight: $font-semibold; |
11 | margin-bottom: 15px; | ||
12 | } | 11 | } |
13 | 12 | ||
14 | & > .contact-admin { | 13 | .contact-admin { |
15 | @include peertube-button; | 14 | @include peertube-button; |
16 | @include orange-button; | 15 | @include orange-button; |
17 | 16 | ||
@@ -19,14 +18,38 @@ | |||
19 | } | 18 | } |
20 | } | 19 | } |
21 | 20 | ||
21 | .instance-badges { | ||
22 | font-size: 16px; | ||
23 | |||
24 | .badge { | ||
25 | font-size: 12px; | ||
26 | font-weight: $font-semibold; | ||
27 | margin-right: 5px; | ||
28 | |||
29 | &.category { | ||
30 | background-color: var(--mainColor); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | |||
22 | .section-title { | 35 | .section-title { |
23 | font-weight: $font-semibold; | 36 | font-weight: $font-semibold; |
24 | font-size: 20px; | 37 | font-size: 16px; |
25 | margin-bottom: 5px; | 38 | margin-bottom: 5px; |
39 | display: flex; | ||
40 | align-items: center; | ||
41 | } | ||
42 | |||
43 | .middle-title { | ||
44 | @include in-content-small-title; | ||
45 | |||
46 | margin-top: 45px; | ||
47 | margin-bottom: 25px; | ||
26 | } | 48 | } |
27 | 49 | ||
28 | .short-description, .description, .terms, .signup { | 50 | .block { |
29 | margin-bottom: 30px; | 51 | margin-bottom: 30px; |
52 | font-size: 15px; | ||
30 | } | 53 | } |
31 | 54 | ||
32 | .short-description .dedicated-to-nsfw { | 55 | .short-description .dedicated-to-nsfw { |
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 a5204de27..16ccae2e2 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts | |||
@@ -4,6 +4,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | 4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' |
5 | import { InstanceService } from '@app/shared/instance/instance.service' | 5 | import { InstanceService } from '@app/shared/instance/instance.service' |
6 | import { MarkdownService } from '@app/shared/renderer' | 6 | import { MarkdownService } from '@app/shared/renderer' |
7 | import { forkJoin } from 'rxjs' | ||
8 | import { first } from 'rxjs/operators' | ||
7 | 9 | ||
8 | @Component({ | 10 | @Component({ |
9 | selector: 'my-about-instance', | 11 | selector: 'my-about-instance', |
@@ -14,8 +16,22 @@ export class AboutInstanceComponent implements OnInit { | |||
14 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent | 16 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent |
15 | 17 | ||
16 | shortDescription = '' | 18 | shortDescription = '' |
17 | descriptionHTML = '' | 19 | |
18 | termsHTML = '' | 20 | html = { |
21 | description: '', | ||
22 | terms: '', | ||
23 | codeOfConduct: '', | ||
24 | moderationInformation: '', | ||
25 | administrator: '', | ||
26 | hardwareInformation: '' | ||
27 | } | ||
28 | |||
29 | creationReason = '' | ||
30 | maintenanceLifetime = '' | ||
31 | businessModel = '' | ||
32 | |||
33 | languages: string[] = [] | ||
34 | categories: string[] = [] | ||
19 | 35 | ||
20 | constructor ( | 36 | constructor ( |
21 | private notifier: Notifier, | 37 | private notifier: Notifier, |
@@ -38,21 +54,30 @@ export class AboutInstanceComponent implements OnInit { | |||
38 | } | 54 | } |
39 | 55 | ||
40 | ngOnInit () { | 56 | ngOnInit () { |
41 | this.instanceService.getAbout() | 57 | forkJoin([ |
42 | .subscribe( | 58 | this.instanceService.getAbout(), |
43 | async res => { | 59 | this.serverService.localeObservable.pipe(first()), |
44 | this.shortDescription = res.instance.shortDescription | 60 | this.serverService.videoLanguagesLoaded.pipe(first()), |
61 | this.serverService.videoCategoriesLoaded.pipe(first()) | ||
62 | ]).subscribe( | ||
63 | async ([ about, translations ]) => { | ||
64 | this.shortDescription = about.instance.shortDescription | ||
45 | 65 | ||
46 | this.descriptionHTML = await this.markdownService.textMarkdownToHTML(res.instance.description) | 66 | this.creationReason = about.instance.creationReason |
47 | this.termsHTML = await this.markdownService.textMarkdownToHTML(res.instance.terms) | 67 | this.maintenanceLifetime = about.instance.maintenanceLifetime |
48 | }, | 68 | this.businessModel = about.instance.businessModel |
49 | 69 | ||
50 | () => this.notifier.error(this.i18n('Cannot get about information from server')) | 70 | this.html = await this.instanceService.buildHtml(about) |
51 | ) | 71 | |
72 | this.languages = this.instanceService.buildTranslatedLanguages(about, translations) | ||
73 | this.categories = this.instanceService.buildTranslatedCategories(about, translations) | ||
74 | }, | ||
75 | |||
76 | () => this.notifier.error(this.i18n('Cannot get about information from server')) | ||
77 | ) | ||
52 | } | 78 | } |
53 | 79 | ||
54 | openContactModal () { | 80 | openContactModal () { |
55 | return this.contactAdminModal.show() | 81 | return this.contactAdminModal.show() |
56 | } | 82 | } |
57 | |||
58 | } | 83 | } |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index fe9d856d0..54115055a 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -2,12 +2,13 @@ | |||
2 | 2 | ||
3 | <ngb-tabset class="root-tabset bootstrap"> | 3 | <ngb-tabset class="root-tabset bootstrap"> |
4 | 4 | ||
5 | <ngb-tab i18n-title title="Basic configuration"> | 5 | <ngb-tab i18n-title title="Instance information"> |
6 | <ng-template ngbTabContent> | 6 | <ng-template ngbTabContent> |
7 | 7 | ||
8 | <div i18n class="inner-form-title">Instance</div> | ||
9 | |||
10 | <ng-container formGroupName="instance"> | 8 | <ng-container formGroupName="instance"> |
9 | |||
10 | <div i18n class="inner-form-title">Instance</div> | ||
11 | |||
11 | <div class="form-group"> | 12 | <div class="form-group"> |
12 | <label i18n for="instanceName">Name</label> | 13 | <label i18n for="instanceName">Name</label> |
13 | <input | 14 | <input |
@@ -20,7 +21,7 @@ | |||
20 | <div class="form-group"> | 21 | <div class="form-group"> |
21 | <label i18n for="instanceShortDescription">Short description</label> | 22 | <label i18n for="instanceShortDescription">Short description</label> |
22 | <textarea | 23 | <textarea |
23 | id="instanceShortDescription" formControlName="shortDescription" | 24 | id="instanceShortDescription" formControlName="shortDescription" class="small" |
24 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" | 25 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" |
25 | ></textarea> | 26 | ></textarea> |
26 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> | 27 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> |
@@ -36,42 +37,56 @@ | |||
36 | </div> | 37 | </div> |
37 | 38 | ||
38 | <div class="form-group"> | 39 | <div class="form-group"> |
39 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 40 | <label i18n for="instanceCategories">Main instance categories</label> |
40 | <my-markdown-textarea | 41 | |
41 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" | 42 | <div> |
42 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | 43 | <p-multiSelect |
43 | ></my-markdown-textarea> | 44 | inputId="instanceCategories" [options]="categoryItems" formControlName="categories" showToggleAll="false" |
44 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | 45 | [defaultLabel]="getDefaultCategoryLabel()" [selectedItemsLabel]="getSelectedCategoryLabel()" |
46 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
47 | ></p-multiSelect> | ||
48 | </div> | ||
45 | </div> | 49 | </div> |
46 | 50 | ||
47 | <div class="form-group"> | 51 | <div class="form-group"> |
48 | <my-peertube-checkbox | 52 | <label i18n for="instanceLanguages">Main languages you/your moderators speak</label> |
49 | inputName="instanceIsNSFW" formControlName="isNSFW" | 53 | |
50 | i18n-labelText labelText="Dedicated to sensitive or NSFW content" | 54 | <div> |
51 | i18n-helpHtml helpHtml="Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br /> | 55 | <p-multiSelect |
52 | Moreover, the NSFW checkbox on video upload will be automatically checked by default." | 56 | inputId="instanceLanguages" [options]="languageItems" formControlName="languages" showToggleAll="false" |
53 | ></my-peertube-checkbox> | 57 | [defaultLabel]="getDefaultLanguageLabel()" [selectedItemsLabel]="getSelectedLanguageLabel()" |
58 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
59 | ></p-multiSelect> | ||
60 | </div> | ||
54 | </div> | 61 | </div> |
55 | 62 | ||
63 | <div i18n class="inner-form-title">Moderation & NSFW</div> | ||
64 | |||
56 | <div class="form-group"> | 65 | <div class="form-group"> |
57 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | 66 | <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW"> |
58 | <div class="peertube-select-container"> | 67 | <ng-template ptTemplate="label"> |
59 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> | 68 | <ng-container i18n>This instance is dedicated to sensitive or NSFW content</ng-container> |
60 | <option i18n value="/videos/overview">Videos Overview</option> | 69 | </ng-template> |
61 | <option i18n value="/videos/trending">Videos Trending</option> | 70 | |
62 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | 71 | <ng-template ptTemplate="help"> |
63 | <option i18n value="/videos/local">Local videos</option> | 72 | <ng-container i18n> |
64 | </select> | 73 | Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br /> |
65 | </div> | 74 | Moreover, the NSFW checkbox on video upload will be automatically checked by default. |
66 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | 75 | </ng-container> |
76 | </ng-template> | ||
77 | </my-peertube-checkbox> | ||
67 | </div> | 78 | </div> |
68 | 79 | ||
69 | <div class="form-group"> | 80 | <div class="form-group"> |
70 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | 81 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> |
71 | <my-help | 82 | |
72 | helpType="custom" i18n-customHtml | 83 | <my-help> |
73 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | 84 | <ng-template ptTemplate="customHtml"> |
74 | ></my-help> | 85 | <ng-container i18n> |
86 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. | ||
87 | </ng-container> | ||
88 | </ng-template> | ||
89 | </my-help> | ||
75 | 90 | ||
76 | <div class="peertube-select-container"> | 91 | <div class="peertube-select-container"> |
77 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> | 92 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> |
@@ -82,10 +97,105 @@ | |||
82 | </div> | 97 | </div> |
83 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> | 98 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> |
84 | </div> | 99 | </div> |
100 | |||
101 | <div class="form-group"> | ||
102 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | ||
103 | <my-markdown-textarea | ||
104 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" | ||
105 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | ||
106 | ></my-markdown-textarea> | ||
107 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | ||
108 | </div> | ||
109 | |||
110 | <div class="form-group"> | ||
111 | <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help> | ||
112 | <my-markdown-textarea | ||
113 | id="instanceCodeOfConduct" formControlName="codeOfConduct" textareaWidth="500px" [previewColumn]="true" | ||
114 | [ngClass]="{ 'input-error': formErrors['instance.codeOfConduct'] }" | ||
115 | ></my-markdown-textarea> | ||
116 | <div *ngIf="formErrors.instance.codeOfConduct" class="form-error">{{ formErrors.instance.codeOfConduct }}</div> | ||
117 | </div> | ||
118 | |||
119 | <div class="form-group"> | ||
120 | <label i18n for="instanceModerationInformation">Moderation information</label><my-help helpType="markdownText"></my-help> | ||
121 | <div class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div> | ||
122 | |||
123 | <my-markdown-textarea | ||
124 | id="instanceModerationInformation" formControlName="moderationInformation" textareaWidth="500px" [previewColumn]="true" | ||
125 | [ngClass]="{ 'input-error': formErrors['instance.moderationInformation'] }" | ||
126 | ></my-markdown-textarea> | ||
127 | <div *ngIf="formErrors.instance.moderationInformation" class="form-error">{{ formErrors.instance.moderationInformation }}</div> | ||
128 | </div> | ||
129 | |||
130 | <div i18n class="inner-form-title">You and your instance</div> | ||
131 | |||
132 | <div class="form-group"> | ||
133 | <label i18n for="instanceAdministrator">Who is behind the instance?</label> | ||
134 | <div class="label-small-info">A single person? A non profit? A company?</div> | ||
135 | |||
136 | <my-markdown-textarea | ||
137 | id="instanceAdministrator" formControlName="administrator" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true" | ||
138 | [classes]="{ 'input-error': formErrors['instance.administrator'] }" | ||
139 | ></my-markdown-textarea> | ||
140 | |||
141 | <div *ngIf="formErrors.instance.administrator" class="form-error">{{ formErrors.instance.administrator }}</div> | ||
142 | </div> | ||
143 | |||
144 | <div class="form-group"> | ||
145 | <label i18n for="instanceCreationReason">Why did you create this instance?</label> | ||
146 | <div class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div> | ||
147 | |||
148 | <textarea | ||
149 | id="instanceCreationReason" formControlName="creationReason" class="small" | ||
150 | [ngClass]="{ 'input-error': formErrors['instance.creationReason'] }" | ||
151 | ></textarea> | ||
152 | <div *ngIf="formErrors.instance.creationReason" class="form-error">{{ formErrors.instance.creationReason }}</div> | ||
153 | </div> | ||
154 | |||
155 | <div class="form-group"> | ||
156 | <label i18n for="instanceMaintenanceLifetime">How long do you plan to maintain this instance?</label> | ||
157 | <div class="label-small-info">It's important to know for users who want to register on your instance</div> | ||
158 | |||
159 | <textarea | ||
160 | id="instanceMaintenanceLifetime" formControlName="maintenanceLifetime" class="small" | ||
161 | [ngClass]="{ 'input-error': formErrors['instance.maintenanceLifetime'] }" | ||
162 | ></textarea> | ||
163 | <div *ngIf="formErrors.instance.maintenanceLifetime" class="form-error">{{ formErrors.instance.maintenanceLifetime }}</div> | ||
164 | </div> | ||
165 | |||
166 | <div class="form-group"> | ||
167 | <label i18n for="instanceBusinessModel">How will you pay the PeerTube instance server?</label> | ||
168 | <div class="label-small-info">With you own funds? With users donations? Advertising?</div> | ||
169 | |||
170 | <textarea | ||
171 | id="instanceBusinessModel" formControlName="businessModel" class="small" | ||
172 | [ngClass]="{ 'input-error': formErrors['instance.businessModel'] }" | ||
173 | ></textarea> | ||
174 | <div *ngIf="formErrors.instance.businessModel" class="form-error">{{ formErrors.instance.businessModel }}</div> | ||
175 | </div> | ||
176 | |||
177 | <div i18n class="inner-form-title">Other information</div> | ||
178 | |||
179 | <div class="form-group"> | ||
180 | <label i18n for="instanceHardwareInformation">On what server/hardware the instance runs?</label> | ||
181 | <div class="label-small-info">2vCore 2GB RAM/or directly the link to the server you rent etc</div> | ||
182 | |||
183 | <my-markdown-textarea | ||
184 | id="instanceHardwareInformation" formControlName="hardwareInformation" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true" | ||
185 | [classes]="{ 'input-error': formErrors['instance.hardwareInformation'] }" | ||
186 | ></my-markdown-textarea> | ||
187 | |||
188 | <div *ngIf="formErrors.instance.hardwareInformation" class="form-error">{{ formErrors.instance.hardwareInformation }}</div> | ||
189 | </div> | ||
190 | |||
85 | </ng-container> | 191 | </ng-container> |
192 | </ng-template> | ||
193 | </ngb-tab> | ||
86 | 194 | ||
195 | <ngb-tab i18n-title title="Basic configuration"> | ||
196 | <ng-template ngbTabContent> | ||
87 | 197 | ||
88 | <div i18n class="inner-form-title">Theme</div> | 198 | <div i18n class="inner-form-title">Theme & Default route</div> |
89 | 199 | ||
90 | <ng-container formGroupName="theme"> | 200 | <ng-container formGroupName="theme"> |
91 | <div class="form-group"> | 201 | <div class="form-group"> |
@@ -102,6 +212,19 @@ | |||
102 | </ng-container> | 212 | </ng-container> |
103 | 213 | ||
104 | 214 | ||
215 | <div class="form-group" formGroupName="instance"> | ||
216 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | ||
217 | <div class="peertube-select-container"> | ||
218 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> | ||
219 | <option i18n value="/videos/overview">Videos Discover</option> | ||
220 | <option i18n value="/videos/trending">Videos Trending</option> | ||
221 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | ||
222 | <option i18n value="/videos/local">Local videos</option> | ||
223 | </select> | ||
224 | </div> | ||
225 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | ||
226 | </div> | ||
227 | |||
105 | <div i18n class="inner-form-title">Signup</div> | 228 | <div i18n class="inner-form-title">Signup</div> |
106 | 229 | ||
107 | <ng-container formGroupName="signup"> | 230 | <ng-container formGroupName="signup"> |
@@ -221,6 +344,41 @@ | |||
221 | </ng-container> | 344 | </ng-container> |
222 | </ng-container> | 345 | </ng-container> |
223 | 346 | ||
347 | <div i18n class="inner-form-title">Instance followings</div> | ||
348 | |||
349 | <ng-container formGroupName="followings"> | ||
350 | <ng-container formGroupName="instance"> | ||
351 | |||
352 | <ng-container formGroupName="autoFollowBack"> | ||
353 | <div class="form-group"> | ||
354 | <my-peertube-checkbox | ||
355 | inputName="followingsInstanceAutoFollowBackEnabled" formControlName="enabled" | ||
356 | i18n-labelText labelText="Automatically follow other instances that follow you" | ||
357 | ></my-peertube-checkbox> | ||
358 | </div> | ||
359 | </ng-container> | ||
360 | |||
361 | <ng-container formGroupName="autoFollowIndex"> | ||
362 | <div class="form-group"> | ||
363 | <my-peertube-checkbox | ||
364 | inputName="followingsInstanceAutoFollowIndexEnabled" formControlName="enabled" | ||
365 | i18n-labelText labelText="Automatically follow instance of the public index (below)" | ||
366 | ></my-peertube-checkbox> | ||
367 | </div> | ||
368 | |||
369 | <div class="form-group"> | ||
370 | <label i18n for="followingsInstanceAutoFollowIndexUrl">Index URL</label> | ||
371 | <input | ||
372 | type="text" id="followingsInstanceAutoFollowIndexUrl" | ||
373 | formControlName="indexUrl" [ngClass]="{ 'input-error': formErrors['followings.instance.autoFollowIndex.indexUrl'] }" | ||
374 | > | ||
375 | <div *ngIf="formErrors.followings.instance.autoFollowIndex.indexUrl" class="form-error">{{ formErrors.followings.instance.autoFollowIndex.indexUrl }}</div> | ||
376 | </div> | ||
377 | |||
378 | </ng-container> | ||
379 | </ng-container> | ||
380 | </ng-container> | ||
381 | |||
224 | 382 | ||
225 | <div i18n class="inner-form-title">Administrator</div> | 383 | <div i18n class="inner-form-title">Administrator</div> |
226 | 384 | ||
@@ -252,10 +410,13 @@ | |||
252 | 410 | ||
253 | <div class="form-group"> | 411 | <div class="form-group"> |
254 | <label i18n for="signupLimit">Your Twitter username</label> | 412 | <label i18n for="signupLimit">Your Twitter username</label> |
255 | <my-help | 413 | |
256 | helpType="custom" i18n-customHtml | 414 | <my-help> |
257 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." | 415 | <ng-template ptTemplate="customHtml"> |
258 | ></my-help> | 416 | <ng-container i18n>Indicates the Twitter account for the website or platform on which the content was published.</ng-container> |
417 | </ng-template> | ||
418 | </my-help> | ||
419 | |||
259 | <input | 420 | <input |
260 | type="text" id="servicesTwitterUsername" | 421 | type="text" id="servicesTwitterUsername" |
261 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" | 422 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" |
@@ -264,13 +425,21 @@ | |||
264 | </div> | 425 | </div> |
265 | 426 | ||
266 | <div class="form-group"> | 427 | <div class="form-group"> |
267 | <my-peertube-checkbox | 428 | <my-peertube-checkbox inputName="servicesTwitterWhitelisted" formControlName="whitelisted"> |
268 | inputName="servicesTwitterWhitelisted" formControlName="whitelisted" | 429 | <ng-template ptTemplate="label"> |
269 | i18n-labelText labelText="Instance whitelisted by Twitter" | 430 | <ng-container i18n>Instance whitelisted by Twitter</ng-container> |
270 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | 431 | </ng-template> |
271 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | 432 | |
272 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | 433 | <ng-template ptTemplate="help"> |
273 | ></my-peertube-checkbox> | 434 | <ng-container i18n> |
435 | If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
436 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
437 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on | ||
438 | <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> | ||
439 | to see if you instance is whitelisted. | ||
440 | </ng-container> | ||
441 | </ng-template> | ||
442 | </my-peertube-checkbox> | ||
274 | </div> | 443 | </div> |
275 | 444 | ||
276 | </ng-container> | 445 | </ng-container> |
@@ -286,11 +455,15 @@ | |||
286 | 455 | ||
287 | <ng-container formGroupName="transcoding"> | 456 | <ng-container formGroupName="transcoding"> |
288 | <div class="form-group"> | 457 | <div class="form-group"> |
289 | <my-peertube-checkbox | 458 | <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled"> |
290 | inputName="transcodingEnabled" formControlName="enabled" | 459 | <ng-template ptTemplate="label"> |
291 | i18n-labelText labelText="Transcoding enabled" | 460 | <ng-container i18n>Transcoding enabled</ng-container> |
292 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | 461 | </ng-template> |
293 | ></my-peertube-checkbox> | 462 | |
463 | <ng-template ptTemplate="help"> | ||
464 | <ng-container i18n>If you disable transcoding, many videos from your users will not work!</ng-container> | ||
465 | </ng-template> | ||
466 | </my-peertube-checkbox> | ||
294 | </div> | 467 | </div> |
295 | 468 | ||
296 | <ng-container *ngIf="isTranscodingEnabled()"> | 469 | <ng-container *ngIf="isTranscodingEnabled()"> |
@@ -299,16 +472,22 @@ | |||
299 | <my-peertube-checkbox | 472 | <my-peertube-checkbox |
300 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" | 473 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" |
301 | i18n-labelText labelText="Allow additional extensions" | 474 | i18n-labelText labelText="Allow additional extensions" |
302 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" | 475 | > |
303 | ></my-peertube-checkbox> | 476 | <ng-template ptTemplate="help"> |
477 | <ng-container i18n>Allow your users to upload .mkv, .mov, .avi, .flv videos</ng-container> | ||
478 | </ng-template> | ||
479 | </my-peertube-checkbox> | ||
304 | </div> | 480 | </div> |
305 | 481 | ||
306 | <div class="form-group"> | 482 | <div class="form-group"> |
307 | <my-peertube-checkbox | 483 | <my-peertube-checkbox |
308 | inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles" | 484 | inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles" |
309 | i18n-labelText labelText="Allow audio files upload" | 485 | i18n-labelText labelText="Allow audio files upload" |
310 | i18n-helpHtml helpHtml="Allow your users to upload audio files that will be merged with the preview file on upload" | 486 | > |
311 | ></my-peertube-checkbox> | 487 | <ng-template ptTemplate="help"> |
488 | <ng-container i18n>Allow your users to upload audio files that will be merged with the preview file on upload</ng-container> | ||
489 | </ng-template> | ||
490 | </my-peertube-checkbox> | ||
312 | </div> | 491 | </div> |
313 | 492 | ||
314 | <div class="form-group"> | 493 | <div class="form-group"> |
@@ -338,10 +517,11 @@ | |||
338 | <div i18n class="inner-form-title"> | 517 | <div i18n class="inner-form-title"> |
339 | Cache | 518 | Cache |
340 | 519 | ||
341 | <my-help | 520 | <my-help> |
342 | helpType="custom" i18n-customHtml | 521 | <ng-template ptTemplate="customHtml"> |
343 | customHtml="Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them." | 522 | <ng-container i18n>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</ng-container> |
344 | ></my-help> | 523 | </ng-template> |
524 | </my-help> | ||
345 | </div> | 525 | </div> |
346 | 526 | ||
347 | <ng-container formGroupName="cache"> | 527 | <ng-container formGroupName="cache"> |
@@ -370,38 +550,45 @@ | |||
370 | <ng-container formGroupName="customizations"> | 550 | <ng-container formGroupName="customizations"> |
371 | <div class="form-group"> | 551 | <div class="form-group"> |
372 | <label i18n for="customizationJavascript">JavaScript</label> | 552 | <label i18n for="customizationJavascript">JavaScript</label> |
373 | <my-help | 553 | <my-help> |
374 | helpType="custom" i18n-customHtml | 554 | <ng-template ptTemplate="customHtml"> |
375 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" | 555 | <ng-container i18n> |
376 | ></my-help> | 556 | Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre> |
557 | </ng-container> | ||
558 | </ng-template> | ||
559 | </my-help> | ||
560 | |||
377 | <textarea | 561 | <textarea |
378 | id="customizationJavascript" formControlName="javascript" | 562 | id="customizationJavascript" formControlName="javascript" |
379 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" | 563 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" |
380 | ></textarea> | 564 | ></textarea> |
565 | |||
381 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> | 566 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> |
382 | </div> | 567 | </div> |
383 | 568 | ||
384 | <div class="form-group"> | 569 | <div class="form-group"> |
385 | <label for="customizationCSS">CSS</label> | 570 | <label for="customizationCSS">CSS</label> |
386 | <my-help | 571 | |
387 | helpType="custom" | 572 | <my-help> |
388 | i18n-customHtml | 573 | <ng-template ptTemplate="customHtml"> |
389 | customHtml=" | 574 | <ng-container i18n> |
390 | Write directly CSS code. Example:<br /><br /> | 575 | Write directly CSS code. Example:<br /><br /> |
391 | <pre> | 576 | <pre> |
392 | #custom-css {{ '{' }} | 577 | #custom-css {{ '{' }} |
393 | color: red; | 578 | color: red; |
394 | {{ '}' }} | 579 | {{ '}' }} |
395 | </pre> | 580 | </pre> |
396 | 581 | ||
397 | Prepend with <em>#custom-css</em> to override styles. Example:<br /><br /> | 582 | Prepend with <em>#custom-css</em> to override styles. Example:<br /><br /> |
398 | <pre> | 583 | <pre> |
399 | #custom-css .logged-in-email {{ '{' }} | 584 | #custom-css .logged-in-email {{ '{' }} |
400 | color: red; | 585 | color: red; |
401 | {{ '}' }} | 586 | {{ '}' }} |
402 | </pre> | 587 | </pre> |
403 | " | 588 | </ng-container> |
404 | ></my-help> | 589 | </ng-template> |
590 | </my-help> | ||
591 | |||
405 | <textarea | 592 | <textarea |
406 | id="customizationCSS" formControlName="css" | 593 | id="customizationCSS" formControlName="css" |
407 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" | 594 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss index c90bd5141..2b4d0da2c 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .form-group { | ||
5 | margin-bottom: 25px; | ||
6 | } | ||
7 | |||
4 | input[type=text] { | 8 | input[type=text] { |
5 | @include peertube-input-text(340px); | 9 | @include peertube-input-text(340px); |
6 | display: block; | 10 | display: block; |
@@ -40,7 +44,12 @@ textarea { | |||
40 | 44 | ||
41 | display: block; | 45 | display: block; |
42 | 46 | ||
43 | &#instanceShortDescription { | 47 | &.small { |
44 | height: 100px; | 48 | height: 75px; |
45 | } | 49 | } |
46 | } | 50 | } |
51 | |||
52 | .label-small-info { | ||
53 | font-style: italic; | ||
54 | margin-bottom: 10px; | ||
55 | } | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 8bd7f7cf6..0a69f3481 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -6,6 +6,9 @@ import { Notifier } from '@app/core' | |||
6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' | 6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
9 | import { SelectItem } from 'primeng/api' | ||
10 | import { forkJoin } from 'rxjs' | ||
11 | import { first } from 'rxjs/operators' | ||
9 | 12 | ||
10 | @Component({ | 13 | @Component({ |
11 | selector: 'my-edit-custom-config', | 14 | selector: 'my-edit-custom-config', |
@@ -18,6 +21,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
18 | resolutions: { id: string, label: string }[] = [] | 21 | resolutions: { id: string, label: string }[] = [] |
19 | transcodingThreadOptions: { label: string, value: number }[] = [] | 22 | transcodingThreadOptions: { label: string, value: number }[] = [] |
20 | 23 | ||
24 | languageItems: SelectItem[] = [] | ||
25 | categoryItems: SelectItem[] = [] | ||
26 | |||
21 | constructor ( | 27 | constructor ( |
22 | protected formValidatorService: FormValidatorService, | 28 | protected formValidatorService: FormValidatorService, |
23 | private customConfigValidatorsService: CustomConfigValidatorsService, | 29 | private customConfigValidatorsService: CustomConfigValidatorsService, |
@@ -88,10 +94,26 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
88 | name: this.customConfigValidatorsService.INSTANCE_NAME, | 94 | name: this.customConfigValidatorsService.INSTANCE_NAME, |
89 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 95 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, |
90 | description: null, | 96 | description: null, |
91 | terms: null, | 97 | |
92 | defaultClientRoute: null, | ||
93 | isNSFW: false, | 98 | isNSFW: false, |
94 | defaultNSFWPolicy: null, | 99 | defaultNSFWPolicy: null, |
100 | |||
101 | terms: null, | ||
102 | codeOfConduct: null, | ||
103 | |||
104 | creationReason: null, | ||
105 | moderationInformation: null, | ||
106 | administrator: null, | ||
107 | maintenanceLifetime: null, | ||
108 | businessModel: null, | ||
109 | |||
110 | hardwareInformation: null, | ||
111 | |||
112 | categories: null, | ||
113 | languages: null, | ||
114 | |||
115 | defaultClientRoute: null, | ||
116 | |||
95 | customizations: { | 117 | customizations: { |
96 | javascript: null, | 118 | javascript: null, |
97 | css: null | 119 | css: null |
@@ -158,6 +180,17 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
158 | enabled: null, | 180 | enabled: null, |
159 | manualApproval: null | 181 | manualApproval: null |
160 | } | 182 | } |
183 | }, | ||
184 | followings: { | ||
185 | instance: { | ||
186 | autoFollowBack: { | ||
187 | enabled: null | ||
188 | }, | ||
189 | autoFollowIndex: { | ||
190 | enabled: null, | ||
191 | indexUrl: this.customConfigValidatorsService.INDEX_URL | ||
192 | } | ||
193 | } | ||
161 | } | 194 | } |
162 | } | 195 | } |
163 | 196 | ||
@@ -173,18 +206,27 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
173 | 206 | ||
174 | this.buildForm(formGroupData) | 207 | this.buildForm(formGroupData) |
175 | 208 | ||
176 | this.configService.getCustomConfig() | 209 | forkJoin([ |
177 | .subscribe( | 210 | this.configService.getCustomConfig(), |
178 | res => { | 211 | this.serverService.videoLanguagesLoaded.pipe(first()), // First so the observable completes |
179 | this.customConfig = res | 212 | this.serverService.videoCategoriesLoaded.pipe(first()) |
213 | ]).subscribe( | ||
214 | ([ config ]) => { | ||
215 | this.customConfig = config | ||
180 | 216 | ||
181 | this.updateForm() | 217 | const languages = this.serverService.getVideoLanguages() |
182 | // Force form validation | 218 | this.languageItems = languages.map(l => ({ label: l.label, value: l.id })) |
183 | this.forceCheck() | ||
184 | }, | ||
185 | 219 | ||
186 | err => this.notifier.error(err.message) | 220 | const categories = this.serverService.getVideoCategories() |
187 | ) | 221 | this.categoryItems = categories.map(l => ({ label: l.label, value: l.id })) |
222 | |||
223 | this.updateForm() | ||
224 | // Force form validation | ||
225 | this.forceCheck() | ||
226 | }, | ||
227 | |||
228 | err => this.notifier.error(err.message) | ||
229 | ) | ||
188 | } | 230 | } |
189 | 231 | ||
190 | isTranscodingEnabled () { | 232 | isTranscodingEnabled () { |
@@ -213,8 +255,23 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
213 | ) | 255 | ) |
214 | } | 256 | } |
215 | 257 | ||
258 | getSelectedLanguageLabel () { | ||
259 | return this.i18n('{{\'{0} languages selected') | ||
260 | } | ||
261 | |||
262 | getDefaultLanguageLabel () { | ||
263 | return this.i18n('No language') | ||
264 | } | ||
265 | |||
266 | getSelectedCategoryLabel () { | ||
267 | return this.i18n('{{\'{0} categories selected') | ||
268 | } | ||
269 | |||
270 | getDefaultCategoryLabel () { | ||
271 | return this.i18n('No category') | ||
272 | } | ||
273 | |||
216 | private updateForm () { | 274 | private updateForm () { |
217 | this.form.patchValue(this.customConfig) | 275 | this.form.patchValue(this.customConfig) |
218 | } | 276 | } |
219 | |||
220 | } | 277 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts index 34febc457..76fabb19d 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts | |||
@@ -43,7 +43,8 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
43 | newUserRegistration: this.i18n('A new user registered on your instance'), | 43 | newUserRegistration: this.i18n('A new user registered on your instance'), |
44 | newFollow: this.i18n('You or your channel(s) has a new follower'), | 44 | newFollow: this.i18n('You or your channel(s) has a new follower'), |
45 | commentMention: this.i18n('Someone mentioned you in video comments'), | 45 | commentMention: this.i18n('Someone mentioned you in video comments'), |
46 | newInstanceFollower: this.i18n('Your instance has a new follower') | 46 | newInstanceFollower: this.i18n('Your instance has a new follower'), |
47 | autoInstanceFollowing: this.i18n('Your instance auto followed another instance') | ||
47 | } | 48 | } |
48 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] | 49 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] |
49 | 50 | ||
@@ -51,7 +52,8 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
51 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, | 52 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, |
52 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, | 53 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, |
53 | newUserRegistration: UserRight.MANAGE_USERS, | 54 | newUserRegistration: UserRight.MANAGE_USERS, |
54 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW | 55 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, |
56 | autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION | ||
55 | } | 57 | } |
56 | 58 | ||
57 | this.emailEnabled = this.serverService.getConfig().email.enabled | 59 | this.emailEnabled = this.serverService.getConfig().email.enabled |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html index 2796dd2db..a11238925 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html | |||
@@ -1,10 +1,13 @@ | |||
1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | 1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> |
2 | <div class="form-group"> | 2 | <div class="form-group"> |
3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> | 3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> |
4 | <my-help | 4 | <my-help> |
5 | helpType="custom" i18n-customHtml | 5 | <ng-template ptTemplate="customHtml"> |
6 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | 6 | <ng-container i18n> |
7 | ></my-help> | 7 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. |
8 | </ng-container> | ||
9 | </ng-template> | ||
10 | </my-help> | ||
8 | 11 | ||
9 | <div class="peertube-select-container"> | 12 | <div class="peertube-select-container"> |
10 | <select id="nsfwPolicy" formControlName="nsfwPolicy"> | 13 | <select id="nsfwPolicy" formControlName="nsfwPolicy"> |
@@ -17,13 +20,15 @@ | |||
17 | 20 | ||
18 | <div class="form-group"> | 21 | <div class="form-group"> |
19 | <label i18n for="videoLanguages">Only display videos in the following languages</label> | 22 | <label i18n for="videoLanguages">Only display videos in the following languages</label> |
20 | <my-help i18n-customHtml | 23 | <my-help> |
21 | customHtml="In Recently added, Trending, Local and Search pages" | 24 | <ng-template ptTemplate="customHtml"> |
22 | ></my-help> | 25 | <ng-container i18n>In Recently added, Trending, Local and Search pages</ng-container> |
26 | </ng-template> | ||
27 | </my-help> | ||
23 | 28 | ||
24 | <div> | 29 | <div> |
25 | <p-multiSelect | 30 | <p-multiSelect |
26 | [options]="languageItems" formControlName="videoLanguages" showToggleAll="true" | 31 | inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" showToggleAll="true" |
27 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" | 32 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" |
28 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | 33 | emptyFilterMessage="No results found" i18n-emptyFilterMessage |
29 | ></p-multiSelect> | 34 | ></p-multiSelect> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts index 77febf179..4fb828082 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts | |||
@@ -5,9 +5,9 @@ import { AuthService } from '../../../core' | |||
5 | import { FormReactive, User, UserService } from '../../../shared' | 5 | import { FormReactive, User, UserService } from '../../../shared' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { Subject } from 'rxjs' | 8 | import { forkJoin, Subject } from 'rxjs' |
9 | import { SelectItem } from 'primeng/api' | 9 | import { SelectItem } from 'primeng/api' |
10 | import { switchMap } from 'rxjs/operators' | 10 | import { first } from 'rxjs/operators' |
11 | 11 | ||
12 | @Component({ | 12 | @Component({ |
13 | selector: 'my-account-video-settings', | 13 | selector: 'my-account-video-settings', |
@@ -39,30 +39,31 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
39 | videoLanguages: null | 39 | videoLanguages: null |
40 | }) | 40 | }) |
41 | 41 | ||
42 | this.serverService.videoLanguagesLoaded | 42 | forkJoin([ |
43 | .pipe(switchMap(() => this.userInformationLoaded)) | 43 | this.serverService.videoLanguagesLoaded.pipe(first()), |
44 | .subscribe(() => { | 44 | this.userInformationLoaded.pipe(first()) |
45 | const languages = this.serverService.getVideoLanguages() | 45 | ]).subscribe(() => { |
46 | 46 | const languages = this.serverService.getVideoLanguages() | |
47 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] | 47 | |
48 | this.languageItems = this.languageItems | 48 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] |
49 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | 49 | this.languageItems = this.languageItems |
50 | 50 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | |
51 | const videoLanguages = this.user.videoLanguages | 51 | |
52 | ? this.user.videoLanguages | 52 | const videoLanguages = this.user.videoLanguages |
53 | : this.languageItems.map(l => l.value) | 53 | ? this.user.videoLanguages |
54 | 54 | : this.languageItems.map(l => l.value) | |
55 | this.form.patchValue({ | 55 | |
56 | nsfwPolicy: this.user.nsfwPolicy, | 56 | this.form.patchValue({ |
57 | webTorrentEnabled: this.user.webTorrentEnabled, | 57 | nsfwPolicy: this.user.nsfwPolicy, |
58 | autoPlayVideo: this.user.autoPlayVideo === true, | 58 | webTorrentEnabled: this.user.webTorrentEnabled, |
59 | videoLanguages | 59 | autoPlayVideo: this.user.autoPlayVideo === true, |
60 | }) | 60 | videoLanguages |
61 | }) | 61 | }) |
62 | }) | ||
62 | } | 63 | } |
63 | 64 | ||
64 | updateDetails () { | 65 | updateDetails () { |
65 | const nsfwPolicy = this.form.value['nsfwPolicy'] | 66 | const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] |
66 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] | 67 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] |
67 | const autoPlayVideo = this.form.value['autoPlayVideo'] | 68 | const autoPlayVideo = this.form.value['autoPlayVideo'] |
68 | 69 | ||
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 571f46de9..6cf1499d3 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -37,7 +37,6 @@ import { | |||
37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' | 37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' |
38 | import { DragDropModule } from '@angular/cdk/drag-drop' | 38 | import { DragDropModule } from '@angular/cdk/drag-drop' |
39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' | 39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' |
40 | import { MultiSelectModule } from 'primeng/multiselect' | ||
41 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' | 40 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' |
42 | 41 | ||
43 | @NgModule({ | 42 | @NgModule({ |
@@ -48,8 +47,7 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account | |||
48 | SharedModule, | 47 | SharedModule, |
49 | TableModule, | 48 | TableModule, |
50 | InputSwitchModule, | 49 | InputSwitchModule, |
51 | DragDropModule, | 50 | DragDropModule |
52 | MultiSelectModule | ||
53 | ], | 51 | ], |
54 | 52 | ||
55 | declarations: [ | 53 | declarations: [ |
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html index 47b3be8cc..4381702ae 100644 --- a/client/src/app/+signup/+register/register-step-user.component.html +++ b/client/src/app/+signup/+register/register-step-user.component.html | |||
@@ -60,11 +60,16 @@ | |||
60 | </div> | 60 | </div> |
61 | 61 | ||
62 | <div class="form-group form-group-terms"> | 62 | <div class="form-group form-group-terms"> |
63 | <my-peertube-checkbox | 63 | <my-peertube-checkbox inputName="terms" formControlName="terms"> |
64 | inputName="terms" formControlName="terms" | 64 | <ng-template ptTemplate="label"> |
65 | i18n-labelHtml | 65 | <ng-container i18n> |
66 | labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance" | 66 | I am at least 16 years old and agree |
67 | ></my-peertube-checkbox> | 67 | to the <a (click)="onTermsClick($event)" href='#'>Terms</a> |
68 | <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> | ||
69 | of this instance | ||
70 | </ng-container> | ||
71 | </ng-template> | ||
72 | </my-peertube-checkbox> | ||
68 | 73 | ||
69 | <div *ngIf="formErrors.terms" class="form-error"> | 74 | <div *ngIf="formErrors.terms" class="form-error"> |
70 | {{ formErrors.terms }} | 75 | {{ formErrors.terms }} |
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts index 3b71fd3c4..6c96f20b4 100644 --- a/client/src/app/+signup/+register/register-step-user.component.ts +++ b/client/src/app/+signup/+register/register-step-user.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2 | import { AuthService } from '@app/core' | 2 | import { AuthService } from '@app/core' |
3 | import { FormReactive, UserService, UserValidatorsService } from '@app/shared' | 3 | import { FormReactive, UserService, UserValidatorsService } from '@app/shared' |
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
@@ -12,7 +12,11 @@ import { concat, of } from 'rxjs' | |||
12 | styleUrls: [ './register.component.scss' ] | 12 | styleUrls: [ './register.component.scss' ] |
13 | }) | 13 | }) |
14 | export class RegisterStepUserComponent extends FormReactive implements OnInit { | 14 | export class RegisterStepUserComponent extends FormReactive implements OnInit { |
15 | @Input() hasCodeOfConduct = false | ||
16 | |||
15 | @Output() formBuilt = new EventEmitter<FormGroup>() | 17 | @Output() formBuilt = new EventEmitter<FormGroup>() |
18 | @Output() termsClick = new EventEmitter<void>() | ||
19 | @Output() codeOfConductClick = new EventEmitter<void>() | ||
16 | 20 | ||
17 | constructor ( | 21 | constructor ( |
18 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
@@ -45,6 +49,16 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit { | |||
45 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) | 49 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) |
46 | } | 50 | } |
47 | 51 | ||
52 | onTermsClick (event: Event) { | ||
53 | event.preventDefault() | ||
54 | this.termsClick.emit() | ||
55 | } | ||
56 | |||
57 | onCodeOfConductClick (event: Event) { | ||
58 | event.preventDefault() | ||
59 | this.codeOfConductClick.emit() | ||
60 | } | ||
61 | |||
48 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { | 62 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { |
49 | const username = this.form.value['username'] || '' | 63 | const username = this.form.value['username'] || '' |
50 | 64 | ||
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html index e4d647fef..906e29aed 100644 --- a/client/src/app/+signup/+register/register.component.html +++ b/client/src/app/+signup/+register/register.component.html | |||
@@ -7,11 +7,15 @@ | |||
7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> | 7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> |
8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> | 8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> |
9 | 9 | ||
10 | <div class="wrapper" *ngIf="!signupDone"> | 10 | <div class="wrapper" [hidden]="signupDone"> |
11 | <div> | 11 | <div class="register-form"> |
12 | <my-custom-stepper linear *ngIf="!signupDone"> | 12 | <my-custom-stepper linear *ngIf="!signupDone"> |
13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User"> | 13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User"> |
14 | <my-register-step-user (formBuilt)="onUserFormBuilt($event)"></my-register-step-user> | 14 | <my-register-step-user |
15 | [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" | ||
16 | (formBuilt)="onUserFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" | ||
17 | > | ||
18 | </my-register-step-user> | ||
15 | 19 | ||
16 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> | 20 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> |
17 | </cdk-step> | 21 | </cdk-step> |
@@ -38,9 +42,56 @@ | |||
38 | </my-custom-stepper> | 42 | </my-custom-stepper> |
39 | </div> | 43 | </div> |
40 | 44 | ||
41 | <div> | 45 | <div class="instance-information"> |
42 | <label i18n>Features found on this instance</label> | 46 | <ngb-accordion [closeOthers]="true" #accordion="ngbAccordion"> |
43 | <my-instance-features-table></my-instance-features-table> | 47 | <ngb-panel id="instance-features" i18n-title title="Features found on this instance"> |
48 | <ng-template ngbPanelContent> | ||
49 | <my-instance-features-table></my-instance-features-table> | ||
50 | </ng-template> | ||
51 | </ngb-panel> | ||
52 | |||
53 | <ng-container *ngIf="about"> | ||
54 | <ngb-panel | ||
55 | *ngIf="aboutHtml.administrator || about.instance.maintenanceLifetime || about.instance.businessModel" | ||
56 | id="admin-sustainability" i18n-title title="Administrators & Sustainability" | ||
57 | > | ||
58 | <ng-template ngbPanelContent> | ||
59 | <div class="block"> | ||
60 | <strong i18n>Who are we?</strong> | ||
61 | <div [innerHTML]="aboutHtml.administrator"></div> | ||
62 | </div> | ||
63 | |||
64 | <div class="block"> | ||
65 | <strong i18n>How long do we plan to maintain this instance?</strong> | ||
66 | <div [innerHTML]="about.instance.maintenanceLifetime"></div> | ||
67 | </div> | ||
68 | |||
69 | <div class="block"> | ||
70 | <strong i18n>How will we pay this instance?</strong> | ||
71 | <div [innerHTML]="about.instance.businessModel"></div> | ||
72 | </div> | ||
73 | </ng-template> | ||
74 | </ngb-panel> | ||
75 | |||
76 | <ngb-panel *ngIf="aboutHtml.moderationInformation" id="moderation-information" i18n-title title="Moderation information"> | ||
77 | <ng-template ngbPanelContent> | ||
78 | <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div> | ||
79 | </ng-template> | ||
80 | </ngb-panel> | ||
81 | |||
82 | <ngb-panel *ngIf="aboutHtml.codeOfConduct" id="code-of-conduct" i18n-title title="Code of conduct"> | ||
83 | <ng-template ngbPanelContent> | ||
84 | <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div> | ||
85 | </ng-template> | ||
86 | </ngb-panel> | ||
87 | |||
88 | <ngb-panel *ngIf="aboutHtml.terms" id="terms" i18n-title title="Terms"> | ||
89 | <ng-template ngbPanelContent> | ||
90 | <div class="block" [innerHTML]="aboutHtml.terms"></div> | ||
91 | </ng-template> | ||
92 | </ngb-panel> | ||
93 | </ng-container> | ||
94 | </ngb-accordion> | ||
44 | </div> | 95 | </div> |
45 | </div> | 96 | </div> |
46 | 97 | ||
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss index 9405b5293..2f62dd59d 100644 --- a/client/src/app/+signup/+register/register.component.scss +++ b/client/src/app/+signup/+register/register.component.scss | |||
@@ -1,5 +1,9 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import "./_bootstrap-variables"; | ||
4 | |||
5 | @import '~bootstrap/scss/functions'; | ||
6 | @import '~bootstrap/scss/variables'; | ||
3 | 7 | ||
4 | .alert { | 8 | .alert { |
5 | font-size: 15px; | 9 | font-size: 15px; |
@@ -13,7 +17,32 @@ | |||
13 | 17 | ||
14 | & > div { | 18 | & > div { |
15 | margin-bottom: 40px; | 19 | margin-bottom: 40px; |
16 | width: 450px; | 20 | |
21 | &.register-form { | ||
22 | width: 450px; | ||
23 | } | ||
24 | |||
25 | &.instance-information { | ||
26 | width: 600px; | ||
27 | margin-bottom: 40px; | ||
28 | |||
29 | .block { | ||
30 | font-size: 15px; | ||
31 | margin-bottom: 15px; | ||
32 | padding: 0 $btn-padding-x; | ||
33 | } | ||
34 | |||
35 | @media screen and (max-width: 1500px) { | ||
36 | width: 450px; | ||
37 | } | ||
38 | |||
39 | ngb-accordion ::ng-deep { | ||
40 | .btn { | ||
41 | font-weight: $font-semibold !important; | ||
42 | color: var(--mainForegroundColor) !important; | ||
43 | } | ||
44 | } | ||
45 | } | ||
17 | 46 | ||
18 | @media screen and (max-width: 500px) { | 47 | @media screen and (max-width: 500px) { |
19 | width: auto; | 48 | width: auto; |
@@ -21,12 +50,6 @@ | |||
21 | } | 50 | } |
22 | } | 51 | } |
23 | 52 | ||
24 | my-instance-features-table { | ||
25 | display: block; | ||
26 | |||
27 | margin-bottom: 40px; | ||
28 | } | ||
29 | |||
30 | .form-group-terms { | 53 | .form-group-terms { |
31 | margin: 30px 0; | 54 | margin: 30px 0; |
32 | } | 55 | } |
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts index cd6059728..d470ef4dc 100644 --- a/client/src/app/+signup/+register/register.component.ts +++ b/client/src/app/+signup/+register/register.component.ts | |||
@@ -1,21 +1,35 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' | 2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' |
3 | import { UserService, UserValidatorsService } from '@app/shared' | 3 | import { UserService, UserValidatorsService } from '@app/shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { UserRegister } from '@shared/models/users/user-register.model' | 5 | import { UserRegister } from '@shared/models/users/user-register.model' |
6 | import { FormGroup } from '@angular/forms' | 6 | import { FormGroup } from '@angular/forms' |
7 | import { About } from '@shared/models/server' | ||
8 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
9 | import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' | ||
7 | 10 | ||
8 | @Component({ | 11 | @Component({ |
9 | selector: 'my-register', | 12 | selector: 'my-register', |
10 | templateUrl: './register.component.html', | 13 | templateUrl: './register.component.html', |
11 | styleUrls: [ './register.component.scss' ] | 14 | styleUrls: [ './register.component.scss' ] |
12 | }) | 15 | }) |
13 | export class RegisterComponent { | 16 | export class RegisterComponent implements OnInit { |
17 | @ViewChild('accordion', { static: true }) accordion: NgbAccordion | ||
18 | |||
14 | info: string = null | 19 | info: string = null |
15 | error: string = null | 20 | error: string = null |
16 | success: string = null | 21 | success: string = null |
17 | signupDone = false | 22 | signupDone = false |
18 | 23 | ||
24 | about: About | ||
25 | aboutHtml = { | ||
26 | description: '', | ||
27 | terms: '', | ||
28 | codeOfConduct: '', | ||
29 | moderationInformation: '', | ||
30 | administrator: '' | ||
31 | } | ||
32 | |||
19 | formStepUser: FormGroup | 33 | formStepUser: FormGroup |
20 | formStepChannel: FormGroup | 34 | formStepChannel: FormGroup |
21 | 35 | ||
@@ -26,6 +40,7 @@ export class RegisterComponent { | |||
26 | private userService: UserService, | 40 | private userService: UserService, |
27 | private serverService: ServerService, | 41 | private serverService: ServerService, |
28 | private redirectService: RedirectService, | 42 | private redirectService: RedirectService, |
43 | private instanceService: InstanceService, | ||
29 | private i18n: I18n | 44 | private i18n: I18n |
30 | ) { | 45 | ) { |
31 | } | 46 | } |
@@ -34,6 +49,19 @@ export class RegisterComponent { | |||
34 | return this.serverService.getConfig().signup.requiresEmailVerification | 49 | return this.serverService.getConfig().signup.requiresEmailVerification |
35 | } | 50 | } |
36 | 51 | ||
52 | ngOnInit (): void { | ||
53 | this.instanceService.getAbout() | ||
54 | .subscribe( | ||
55 | async about => { | ||
56 | this.about = about | ||
57 | |||
58 | this.aboutHtml = await this.instanceService.buildHtml(about) | ||
59 | }, | ||
60 | |||
61 | err => this.notifier.error(err.message) | ||
62 | ) | ||
63 | } | ||
64 | |||
37 | hasSameChannelAndAccountNames () { | 65 | hasSameChannelAndAccountNames () { |
38 | return this.getUsername() === this.getChannelName() | 66 | return this.getUsername() === this.getChannelName() |
39 | } | 67 | } |
@@ -58,6 +86,14 @@ export class RegisterComponent { | |||
58 | this.formStepChannel = form | 86 | this.formStepChannel = form |
59 | } | 87 | } |
60 | 88 | ||
89 | onTermsClick () { | ||
90 | if (this.accordion) this.accordion.toggle('terms') | ||
91 | } | ||
92 | |||
93 | onCodeOfConductClick () { | ||
94 | if (this.accordion) this.accordion.toggle('code-of-conduct') | ||
95 | } | ||
96 | |||
61 | signup () { | 97 | signup () { |
62 | this.error = null | 98 | this.error = null |
63 | 99 | ||
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts index 46336cbd0..e55f83990 100644 --- a/client/src/app/+signup/+register/register.module.ts +++ b/client/src/app/+signup/+register/register.module.ts | |||
@@ -7,13 +7,15 @@ import { RegisterStepChannelComponent } from './register-step-channel.component' | |||
7 | import { RegisterStepUserComponent } from './register-step-user.component' | 7 | import { RegisterStepUserComponent } from './register-step-user.component' |
8 | import { CustomStepperComponent } from './custom-stepper.component' | 8 | import { CustomStepperComponent } from './custom-stepper.component' |
9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' | 9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' |
10 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' | ||
10 | 11 | ||
11 | @NgModule({ | 12 | @NgModule({ |
12 | imports: [ | 13 | imports: [ |
13 | RegisterRoutingModule, | 14 | RegisterRoutingModule, |
14 | SharedModule, | 15 | SharedModule, |
15 | CdkStepperModule, | 16 | CdkStepperModule, |
16 | SignupSharedModule | 17 | SignupSharedModule, |
18 | NgbAccordionModule | ||
17 | ], | 19 | ], |
18 | 20 | ||
19 | declarations: [ | 21 | declarations: [ |
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 07a576083..81b4351c5 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -54,3 +54,8 @@ | |||
54 | </div> | 54 | </div> |
55 | </ng-template> | 55 | </ng-template> |
56 | </p-toast> | 56 | </p-toast> |
57 | |||
58 | <ng-template [ngIf]="isUserLoggedIn()"> | ||
59 | <my-welcome-modal #welcomeModal></my-welcome-modal> | ||
60 | <my-instance-config-warning-modal #instanceConfigWarningModal></my-instance-config-warning-modal> | ||
61 | </ng-template> | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 64bfb9671..6b18e5feb 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' | 3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' |
4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' | 4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' |
5 | import { is18nPath } from '../../../shared/models/i18n' | 5 | import { is18nPath } from '../../../shared/models/i18n' |
6 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators' | 7 | import { debounceTime, filter, map, pairwise, skip, switchMap } from 'rxjs/operators' |
8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { fromEvent } from 'rxjs' | 10 | import { fromEvent } from 'rxjs' |
@@ -13,6 +13,11 @@ import { PluginService } from '@app/core/plugins/plugin.service' | |||
13 | import { HooksService } from '@app/core/plugins/hooks.service' | 13 | import { HooksService } from '@app/core/plugins/hooks.service' |
14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | 15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' |
16 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
17 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
18 | import { UserRole } from '@shared/models' | ||
19 | import { User } from '@app/shared' | ||
20 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
16 | 21 | ||
17 | @Component({ | 22 | @Component({ |
18 | selector: 'my-app', | 23 | selector: 'my-app', |
@@ -20,6 +25,9 @@ import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | |||
20 | styleUrls: [ './app.component.scss' ] | 25 | styleUrls: [ './app.component.scss' ] |
21 | }) | 26 | }) |
22 | export class AppComponent implements OnInit { | 27 | export class AppComponent implements OnInit { |
28 | @ViewChild('welcomeModal', { static: false }) welcomeModal: WelcomeModalComponent | ||
29 | @ViewChild('instanceConfigWarningModal', { static: false }) instanceConfigWarningModal: InstanceConfigWarningModalComponent | ||
30 | |||
23 | isMenuDisplayed = true | 31 | isMenuDisplayed = true |
24 | isMenuChangedByUser = false | 32 | isMenuChangedByUser = false |
25 | 33 | ||
@@ -32,6 +40,7 @@ export class AppComponent implements OnInit { | |||
32 | private authService: AuthService, | 40 | private authService: AuthService, |
33 | private serverService: ServerService, | 41 | private serverService: ServerService, |
34 | private pluginService: PluginService, | 42 | private pluginService: PluginService, |
43 | private instanceService: InstanceService, | ||
35 | private domSanitizer: DomSanitizer, | 44 | private domSanitizer: DomSanitizer, |
36 | private redirectService: RedirectService, | 45 | private redirectService: RedirectService, |
37 | private screenService: ScreenService, | 46 | private screenService: ScreenService, |
@@ -96,6 +105,8 @@ export class AppComponent implements OnInit { | |||
96 | .subscribe(() => this.onResize()) | 105 | .subscribe(() => this.onResize()) |
97 | 106 | ||
98 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) | 107 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) |
108 | |||
109 | this.openModalsIfNeeded() | ||
99 | } | 110 | } |
100 | 111 | ||
101 | isUserLoggedIn () { | 112 | isUserLoggedIn () { |
@@ -220,32 +231,66 @@ export class AppComponent implements OnInit { | |||
220 | this.hooks.runAction('action:application.init', 'common') | 231 | this.hooks.runAction('action:application.init', 'common') |
221 | } | 232 | } |
222 | 233 | ||
234 | private async openModalsIfNeeded () { | ||
235 | this.serverService.configLoaded | ||
236 | .pipe( | ||
237 | switchMap(() => this.authService.userInformationLoaded), | ||
238 | map(() => this.authService.getUser()), | ||
239 | filter(user => user.role === UserRole.ADMINISTRATOR) | ||
240 | ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template | ||
241 | } | ||
242 | |||
243 | private async openAdminModals (user: User) { | ||
244 | if (user.noWelcomeModal !== true) return this.welcomeModal.show() | ||
245 | |||
246 | const config = this.serverService.getConfig() | ||
247 | if (user.noInstanceConfigWarningModal === true || !config.signup.allowed) return | ||
248 | |||
249 | this.instanceService.getAbout() | ||
250 | .subscribe(about => { | ||
251 | if ( | ||
252 | config.instance.name.toLowerCase() === 'peertube' || | ||
253 | !about.instance.terms || | ||
254 | !about.instance.administrator || | ||
255 | !about.instance.maintenanceLifetime | ||
256 | ) { | ||
257 | this.instanceConfigWarningModal.show(about) | ||
258 | } | ||
259 | }) | ||
260 | } | ||
261 | |||
223 | private initHotkeys () { | 262 | private initHotkeys () { |
224 | this.hotkeysService.add([ | 263 | this.hotkeysService.add([ |
225 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { | 264 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { |
226 | document.getElementById('search-video').focus() | 265 | document.getElementById('search-video').focus() |
227 | return false | 266 | return false |
228 | }, undefined, this.i18n('Focus the search bar')), | 267 | }, undefined, this.i18n('Focus the search bar')), |
268 | |||
229 | new Hotkey('b', (event: KeyboardEvent): boolean => { | 269 | new Hotkey('b', (event: KeyboardEvent): boolean => { |
230 | this.toggleMenu() | 270 | this.toggleMenu() |
231 | return false | 271 | return false |
232 | }, undefined, this.i18n('Toggle the left menu')), | 272 | }, undefined, this.i18n('Toggle the left menu')), |
273 | |||
233 | new Hotkey('g o', (event: KeyboardEvent): boolean => { | 274 | new Hotkey('g o', (event: KeyboardEvent): boolean => { |
234 | this.router.navigate([ '/videos/overview' ]) | 275 | this.router.navigate([ '/videos/overview' ]) |
235 | return false | 276 | return false |
236 | }, undefined, this.i18n('Go to the discover videos page')), | 277 | }, undefined, this.i18n('Go to the discover videos page')), |
278 | |||
237 | new Hotkey('g t', (event: KeyboardEvent): boolean => { | 279 | new Hotkey('g t', (event: KeyboardEvent): boolean => { |
238 | this.router.navigate([ '/videos/trending' ]) | 280 | this.router.navigate([ '/videos/trending' ]) |
239 | return false | 281 | return false |
240 | }, undefined, this.i18n('Go to the trending videos page')), | 282 | }, undefined, this.i18n('Go to the trending videos page')), |
283 | |||
241 | new Hotkey('g r', (event: KeyboardEvent): boolean => { | 284 | new Hotkey('g r', (event: KeyboardEvent): boolean => { |
242 | this.router.navigate([ '/videos/recently-added' ]) | 285 | this.router.navigate([ '/videos/recently-added' ]) |
243 | return false | 286 | return false |
244 | }, undefined, this.i18n('Go to the recently added videos page')), | 287 | }, undefined, this.i18n('Go to the recently added videos page')), |
288 | |||
245 | new Hotkey('g l', (event: KeyboardEvent): boolean => { | 289 | new Hotkey('g l', (event: KeyboardEvent): boolean => { |
246 | this.router.navigate([ '/videos/local' ]) | 290 | this.router.navigate([ '/videos/local' ]) |
247 | return false | 291 | return false |
248 | }, undefined, this.i18n('Go to the local videos page')), | 292 | }, undefined, this.i18n('Go to the local videos page')), |
293 | |||
249 | new Hotkey('g u', (event: KeyboardEvent): boolean => { | 294 | new Hotkey('g u', (event: KeyboardEvent): boolean => { |
250 | this.router.navigate([ '/videos/upload' ]) | 295 | this.router.navigate([ '/videos/upload' ]) |
251 | return false | 296 | return false |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 1e2936a37..a3ea33ca9 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -18,6 +18,8 @@ import { VideosModule } from './videos' | |||
18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' | 18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' |
19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
20 | import { SearchModule } from '@app/search' | 20 | import { SearchModule } from '@app/search' |
21 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
22 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
21 | 23 | ||
22 | export function metaFactory (serverService: ServerService): MetaLoader { | 24 | export function metaFactory (serverService: ServerService): MetaLoader { |
23 | return new MetaStaticLoader({ | 25 | return new MetaStaticLoader({ |
@@ -39,7 +41,10 @@ export function metaFactory (serverService: ServerService): MetaLoader { | |||
39 | MenuComponent, | 41 | MenuComponent, |
40 | LanguageChooserComponent, | 42 | LanguageChooserComponent, |
41 | AvatarNotificationComponent, | 43 | AvatarNotificationComponent, |
42 | HeaderComponent | 44 | HeaderComponent, |
45 | |||
46 | WelcomeModalComponent, | ||
47 | InstanceConfigWarningModalComponent | ||
43 | ], | 48 | ], |
44 | imports: [ | 49 | imports: [ |
45 | BrowserModule, | 50 | BrowserModule, |
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 4efe3fb22..683355960 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -23,10 +23,11 @@ | |||
23 | or create an account on another instance | 23 | or create an account on another instance |
24 | </a> | 24 | </a> |
25 | 25 | ||
26 | <my-help | 26 | <my-help *ngIf="signupAllowed === false"> |
27 | *ngIf="signupAllowed === false" helpType="custom" i18n-customHtml | 27 | <ng-template ptTemplate="customHtml"> |
28 | customHtml="User registration is not allowed on this instance, but you can register on many others!" | 28 | <ng-container i18n>User registration is not allowed on this instance, but you can register on many others!</ng-container> |
29 | ></my-help> | 29 | </ng-template> |
30 | </my-help> | ||
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | <div *ngIf="formErrors.username" class="form-error"> | 33 | <div *ngIf="formErrors.username" class="form-error"> |
diff --git a/client/src/app/modal/instance-config-warning-modal.component.html b/client/src/app/modal/instance-config-warning-modal.component.html new file mode 100644 index 000000000..64f14e69b --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.html | |||
@@ -0,0 +1,45 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Configuration warning!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <p i18n>Hello dear administrator. You enabled user registration on your instance but you did not configure the following fields:</p> | ||
10 | |||
11 | <ul> | ||
12 | <li i18n *ngIf="about.instance.name.toLowerCase() === 'peertube'">Instance name</li> | ||
13 | <li i18n *ngIf="isDefaultShortDescription(about.instance.shortDescription)">Instance short description</li> | ||
14 | |||
15 | <li i18n *ngIf="!about.instance.administrator">Who you are</li> | ||
16 | <li i18n *ngIf="!about.instance.maintenanceLifetime">How long you plan to maintain your instance</li> | ||
17 | <li i18n *ngIf="!about.instance.businessModel">How you plan to pay your instance</li> | ||
18 | |||
19 | <li i18n *ngIf="!about.instance.moderationInformation">How you will moderate your instance</li> | ||
20 | <li i18n *ngIf="!about.instance.terms">Instance terms</li> | ||
21 | </ul> | ||
22 | |||
23 | <p> | ||
24 | Please consider to configure these fields to help people to choose <strong>the appropriate instance</strong>. | ||
25 | Without them, your instance may not be referenced on <a target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">JoinPeerTube website</a>. | ||
26 | </p> | ||
27 | |||
28 | <div class="configure-instance"> | ||
29 | <a i18n href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure these fields</a> | ||
30 | </div> | ||
31 | |||
32 | </div> | ||
33 | |||
34 | <div class="modal-footer inputs"> | ||
35 | <my-peertube-checkbox | ||
36 | inputName="stopDisplayModal" [(ngModel)]="stopDisplayModal" | ||
37 | i18n-labelText labelText="Don't show me this warning anymore" | ||
38 | > | ||
39 | |||
40 | </my-peertube-checkbox> | ||
41 | |||
42 | <span i18n class="action-button action-button-cancel" (click)="hide()">Close</span> | ||
43 | </div> | ||
44 | |||
45 | </ng-template> | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.scss b/client/src/app/modal/instance-config-warning-modal.component.scss new file mode 100644 index 000000000..ff62a1b9c --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.scss | |||
@@ -0,0 +1,22 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .action-button-cancel { | ||
5 | margin-right: 0 !important; | ||
6 | } | ||
7 | |||
8 | .modal-body { | ||
9 | font-size: 15px; | ||
10 | } | ||
11 | |||
12 | li { | ||
13 | margin-bottom: 10px; | ||
14 | } | ||
15 | |||
16 | .configure-instance { | ||
17 | text-align: center; | ||
18 | font-weight: 600; | ||
19 | font-size: 18px; | ||
20 | margin-top: 40px; | ||
21 | margin-bottom: 10px; | ||
22 | } | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts new file mode 100644 index 000000000..742a7dd41 --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { About } from '@shared/models/server' | ||
5 | import { UserService } from '@app/shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-instance-config-warning-modal', | ||
9 | templateUrl: './instance-config-warning-modal.component.html', | ||
10 | styleUrls: [ './instance-config-warning-modal.component.scss' ] | ||
11 | }) | ||
12 | export class InstanceConfigWarningModalComponent { | ||
13 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
14 | |||
15 | stopDisplayModal = false | ||
16 | about: About | ||
17 | |||
18 | constructor ( | ||
19 | private userService: UserService, | ||
20 | private modalService: NgbModal, | ||
21 | private notifier: Notifier | ||
22 | ) { } | ||
23 | |||
24 | show (about: About) { | ||
25 | this.about = about | ||
26 | |||
27 | const ref = this.modalService.open(this.modal) | ||
28 | |||
29 | ref.result.finally(() => { | ||
30 | if (this.stopDisplayModal === true) this.doNotOpenAgain() | ||
31 | }) | ||
32 | } | ||
33 | |||
34 | isDefaultShortDescription (description: string) { | ||
35 | return description === 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly ' + | ||
36 | 'in the web browser with WebTorrent and Angular.' | ||
37 | } | ||
38 | |||
39 | private doNotOpenAgain () { | ||
40 | this.userService.updateMyProfile({ noInstanceConfigWarningModal: true }) | ||
41 | .subscribe( | ||
42 | () => console.log('We will not open the instance config warning modal again.'), | ||
43 | |||
44 | err => this.notifier.error(err.message) | ||
45 | ) | ||
46 | } | ||
47 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.html b/client/src/app/modal/welcome-modal.component.html new file mode 100644 index 000000000..09ff2163b --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.html | |||
@@ -0,0 +1,67 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Welcome on PeerTube dear administrator!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <div class="block-documentation"> | ||
10 | <div i18n class="subtitle">Documentation</div> | ||
11 | |||
12 | <div class="columns"> | ||
13 | <a class="link-block" href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer"> | ||
14 | <a class="link-title" href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer">CLI</a> | ||
15 | |||
16 | <div>Upload or import videos, parse logs, prune storage directories, reset user password...</div> | ||
17 | </a> | ||
18 | |||
19 | <a class="link-block" href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer"> | ||
20 | <a class="link-title" href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer">Administer</a> | ||
21 | |||
22 | <div>Managing users, following other instances, dealing with spammers...</div> | ||
23 | </a> | ||
24 | |||
25 | <a class="link-block" href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer"> | ||
26 | <a class="link-title" href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">Use</a> | ||
27 | |||
28 | <div>Setup your account, managing video playlists, discover third-party applications...</div> | ||
29 | </a> | ||
30 | </div> | ||
31 | </div> | ||
32 | |||
33 | <div class="block-configuration"> | ||
34 | <div i18n class="subtitle">It's time to configure your instance!</div> | ||
35 | |||
36 | <p i18n> | ||
37 | Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>, specifying <strong>who you are</strong>, | ||
38 | why <strong>you created your instance</strong> and <strong>how long</strong> you plan to <strong>maintain your it</strong> | ||
39 | is very important for visitors to understand on what type of instance they are. | ||
40 | </p> | ||
41 | |||
42 | <p i18n> | ||
43 | If you want to open registrations, please decide what are <strong>your moderation rules</strong>, fill your <strong>instance terms</strong> | ||
44 | and specify the categories and languages you speak. This way, you will help users to register on <strong>the appropriate</strong> PeerTube instance. | ||
45 | </p> | ||
46 | |||
47 | <div class="configure-instance"> | ||
48 | <a i18n href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure your instance</a> | ||
49 | </div> | ||
50 | </div> | ||
51 | |||
52 | <div class="block-links"> | ||
53 | <div i18n class="subtitle">Useful links</div> | ||
54 | |||
55 | <ul> | ||
56 | <li>Official PeerTube website (news, support, contribute...): <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">https://joinpeertube.org</a></li> | ||
57 | |||
58 | <li>Put your instance on the public PeerTube index: <a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a></li> | ||
59 | </ul> | ||
60 | </div> | ||
61 | </div> | ||
62 | |||
63 | <div class="modal-footer inputs"> | ||
64 | <span i18n class="action-button action-button-submit" (click)="hide()">Understood!</span> | ||
65 | </div> | ||
66 | |||
67 | </ng-template> | ||
diff --git a/client/src/app/modal/welcome-modal.component.scss b/client/src/app/modal/welcome-modal.component.scss new file mode 100644 index 000000000..8bb6973f4 --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.scss | |||
@@ -0,0 +1,56 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .subtitle { | ||
9 | font-weight: $font-semibold; | ||
10 | margin-bottom: 10px; | ||
11 | font-size: 16px; | ||
12 | } | ||
13 | |||
14 | .block-documentation .subtitle { | ||
15 | margin-bottom: 20px; | ||
16 | } | ||
17 | |||
18 | .block-configuration, | ||
19 | .block-instance { | ||
20 | margin-top: 30px; | ||
21 | } | ||
22 | |||
23 | li { | ||
24 | margin-bottom: 10px; | ||
25 | } | ||
26 | |||
27 | .configure-instance { | ||
28 | text-align: center; | ||
29 | font-weight: 600; | ||
30 | font-size: 18px; | ||
31 | margin: 20px 0 40px 0; | ||
32 | } | ||
33 | |||
34 | .columns { | ||
35 | display: flex; | ||
36 | |||
37 | .link-block { | ||
38 | @include disable-default-a-behaviour; | ||
39 | |||
40 | color: var(--mainForegroundColor); | ||
41 | padding: 10px; | ||
42 | transition: background-color 0.2s ease-in; | ||
43 | |||
44 | &:hover { | ||
45 | background-color: rgba(0, 0, 0, 0.05); | ||
46 | } | ||
47 | |||
48 | .link-title { | ||
49 | font-size: 16px; | ||
50 | font-weight: $font-semibold; | ||
51 | display: flex; | ||
52 | justify-content: center; | ||
53 | margin-bottom: 5px; | ||
54 | } | ||
55 | } | ||
56 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts new file mode 100644 index 000000000..05412a4cd --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { UserService } from '@app/shared' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-welcome-modal', | ||
8 | templateUrl: './welcome-modal.component.html', | ||
9 | styleUrls: [ './welcome-modal.component.scss' ] | ||
10 | }) | ||
11 | export class WelcomeModalComponent { | ||
12 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
13 | |||
14 | constructor ( | ||
15 | private userService: UserService, | ||
16 | private modalService: NgbModal, | ||
17 | private notifier: Notifier | ||
18 | ) { } | ||
19 | |||
20 | show () { | ||
21 | const ref = this.modalService.open(this.modal,{ | ||
22 | backdrop: 'static', | ||
23 | keyboard: false, | ||
24 | size: 'lg' | ||
25 | }) | ||
26 | |||
27 | ref.result.finally(() => this.doNotOpenAgain()) | ||
28 | } | ||
29 | |||
30 | private doNotOpenAgain () { | ||
31 | this.userService.updateMyProfile({ noWelcomeModal: true }) | ||
32 | .subscribe( | ||
33 | () => console.log('We will not open the welcome modal again.'), | ||
34 | |||
35 | err => this.notifier.error(err.message) | ||
36 | ) | ||
37 | } | ||
38 | } | ||
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 | }) |
6 | export class PeerTubeTemplateDirective { | 6 | export 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 @@ | |||
1 | import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core' | 1 | import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { 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 | }) |
16 | export class PeertubeCheckboxComponent implements ControlValueAccessor { | 17 | export 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 @@ | |||
1 | import { 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 | }) | ||
8 | export 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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { 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 | }) |
10 | export class InstanceFeaturesTableComponent implements OnInit { | 11 | export 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' | |||
4 | import { environment } from '../../../environments/environment' | 4 | import { environment } from '../../../environments/environment' |
5 | import { RestExtractor, RestService } from '../rest' | 5 | import { RestExtractor, RestService } from '../rest' |
6 | import { About } from '../../../../../shared/models/server' | 6 | import { About } from '../../../../../shared/models/server' |
7 | import { MarkdownService } from '@app/shared/renderer' | ||
8 | import { peertubeTranslate } from '@shared/models' | ||
9 | import { ServerService } from '@app/core' | ||
7 | 10 | ||
8 | @Injectable() | 11 | @Injectable() |
9 | export class InstanceService { | 12 | export 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 @@ | |||
1 | import { Component, Input, OnChanges, OnInit } from '@angular/core' | 1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { MarkdownService } from '@app/shared/renderer' | 3 | import { MarkdownService } from '@app/shared/renderer' |
4 | import { 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 | ||
11 | export class HelpComponent implements OnInit, OnChanges { | 12 | export 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' | |||
6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | 6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' |
7 | import { HelpComponent } from '@app/shared/misc/help.component' | 7 | import { HelpComponent } from '@app/shared/misc/help.component' |
8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | 8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' |
9 | |||
10 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | 9 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' |
11 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' | 10 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' |
12 | |||
13 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | 11 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' |
14 | import { ButtonComponent } from './buttons/button.component' | 12 | import { ButtonComponent } from './buttons/button.component' |
15 | import { DeleteButtonComponent } from './buttons/delete-button.component' | 13 | import { DeleteButtonComponent } from './buttons/delete-button.component' |
@@ -93,6 +91,8 @@ import { VideoDownloadComponent } from '@app/shared/video/modals/video-download. | |||
93 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' | 91 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' |
94 | import { ClipboardModule } from 'ngx-clipboard' | 92 | import { ClipboardModule } from 'ngx-clipboard' |
95 | import { FollowService } from '@app/shared/instance/follow.service' | 93 | import { FollowService } from '@app/shared/instance/follow.service' |
94 | import { MultiSelectModule } from 'primeng/multiselect' | ||
95 | import { 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[]>() |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index 217cadc66..245ae42b6 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -15,7 +15,16 @@ | |||
15 | 15 | ||
16 | <div class="form-group"> | 16 | <div class="form-group"> |
17 | <label i18n class="label-tags">Tags</label> | 17 | <label i18n class="label-tags">Tags</label> |
18 | <my-help i18n-preHtml preHtml="Tags could be used to suggest relevant recommendations.</br>Press Enter to add a new tag."></my-help> | 18 | |
19 | <my-help> | ||
20 | <ng-template ptTemplate="customHtml"> | ||
21 | <ng-container i18n> | ||
22 | Tags could be used to suggest relevant recommendations. <br /> | ||
23 | Press Enter to add a new tag. | ||
24 | </ng-container> | ||
25 | </ng-template> | ||
26 | </my-help> | ||
27 | |||
19 | <tag-input | 28 | <tag-input |
20 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | 29 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" |
21 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" | 30 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" |
@@ -25,7 +34,15 @@ | |||
25 | 34 | ||
26 | <div class="form-group"> | 35 | <div class="form-group"> |
27 | <label i18n for="description">Description</label> | 36 | <label i18n for="description">Description</label> |
28 | <my-help helpType="markdownText" i18n-preHtml preHtml="Video descriptions are truncated by default and require manual action to expand them."></my-help> | 37 | |
38 | <my-help helpType="markdownText"> | ||
39 | <ng-template ptTemplate="preHtml"> | ||
40 | <ng-container i18n> | ||
41 | Video descriptions are truncated by default and require manual action to expand them. | ||
42 | </ng-container> | ||
43 | </ng-template> | ||
44 | </my-help> | ||
45 | |||
29 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> | 46 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> |
30 | 47 | ||
31 | <div *ngIf="formErrors.description" class="form-error"> | 48 | <div *ngIf="formErrors.description" class="form-error"> |
@@ -114,20 +131,25 @@ | |||
114 | </div> | 131 | </div> |
115 | </div> | 132 | </div> |
116 | 133 | ||
117 | <my-peertube-checkbox | 134 | <my-peertube-checkbox inputName="nsfw" formControlName="nsfw" helpPlacement="bottom-right"> |
118 | inputName="nsfw" formControlName="nsfw" | 135 | <ng-template ptTemplate="label"> |
119 | i18n-labelText labelText="This video contains mature or explicit content" | 136 | <ng-container i18n>This video contains mature or explicit content</ng-container> |
120 | i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default." | 137 | </ng-template> |
121 | helpPlacement="bottom-right" | 138 | |
122 | ></my-peertube-checkbox> | 139 | <ng-template ptTemplate="help"> |
123 | 140 | <ng-container i18n>Some instances do not list videos containing mature or explicit content by default.</ng-container> | |
124 | <my-peertube-checkbox | 141 | </ng-template> |
125 | *ngIf="waitTranscodingEnabled" | 142 | </my-peertube-checkbox> |
126 | inputName="waitTranscoding" formControlName="waitTranscoding" | 143 | |
127 | i18n-labelText labelText="Wait transcoding before publishing the video" | 144 | <my-peertube-checkbox *ngIf="waitTranscodingEnabled" inputName="waitTranscoding" formControlName="waitTranscoding" helpPlacement="bottom-right"> |
128 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." | 145 | <ng-template ptTemplate="label"> |
129 | helpPlacement="bottom-right" | 146 | <ng-container i18n>Wait transcoding before publishing the video</ng-container> |
130 | ></my-peertube-checkbox> | 147 | </ng-template> |
148 | |||
149 | <ng-template ptTemplate="help"> | ||
150 | <ng-container i18n>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</ng-container> | ||
151 | </ng-template> | ||
152 | </my-peertube-checkbox> | ||
131 | 153 | ||
132 | </div> | 154 | </div> |
133 | </div> | 155 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html index 7a495fea5..c290fd4b1 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html | |||
@@ -12,10 +12,14 @@ | |||
12 | 12 | ||
13 | <div class="form-group form-group-magnet-uri"> | 13 | <div class="form-group form-group-magnet-uri"> |
14 | <label i18n for="magnetUri">Paste magnet URI</label> | 14 | <label i18n for="magnetUri">Paste magnet URI</label> |
15 | <my-help | 15 | <my-help> |
16 | helpType="custom" i18n-customHtml | 16 | <ng-template ptTemplate="customHtml"> |
17 | customHtml="You can import any torrent file that points to a mp4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance." | 17 | <ng-container i18n> |
18 | ></my-help> | 18 | You can import any torrent file that points to a mp4 file. |
19 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | ||
20 | </ng-container> | ||
21 | </ng-template> | ||
22 | </my-help> | ||
19 | 23 | ||
20 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> | 24 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> |
21 | </div> | 25 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html index e4f19faa8..09d0b8272 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html | |||
@@ -4,10 +4,16 @@ | |||
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <label i18n for="targetUrl">URL</label> | 6 | <label i18n for="targetUrl">URL</label> |
7 | <my-help | 7 | |
8 | helpType="custom" i18n-customHtml | 8 | <my-help> |
9 | customHtml="You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> or URL that points to a raw MP4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance." | 9 | <ng-template ptTemplate="customHtml"> |
10 | ></my-help> | 10 | <ng-container i18n> |
11 | You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> | ||
12 | or URL that points to a raw MP4 file. | ||
13 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | ||
14 | </ng-container> | ||
15 | </ng-template> | ||
16 | </my-help> | ||
11 | 17 | ||
12 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> | 18 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> |
13 | </div> | 19 | </div> |
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index abbc137b2..ebd3c0cc4 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -343,6 +343,7 @@ | |||
343 | & + span { | 343 | & + span { |
344 | position: relative; | 344 | position: relative; |
345 | width: 18px; | 345 | width: 18px; |
346 | min-width: 18px; | ||
346 | height: 18px; | 347 | height: 18px; |
347 | border: $border-width solid var(--mainForegroundColor); | 348 | border: $border-width solid var(--mainForegroundColor); |
348 | border-radius: 3px; | 349 | border-radius: 3px; |
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 1a5144b11..4bf48a570 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss | |||
@@ -26,11 +26,6 @@ body { | |||
26 | .vjs-dock-description { | 26 | .vjs-dock-description { |
27 | font-size: 11px; | 27 | font-size: 11px; |
28 | 28 | ||
29 | .text::before, .text::after { | ||
30 | display: inline-block; | ||
31 | content: '\1F308'; | ||
32 | } | ||
33 | |||
34 | .text::before { | 29 | .text::before { |
35 | margin-right: 4px; | 30 | margin-right: 4px; |
36 | } | 31 | } |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 6ff3efef1..19d2a1d02 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -239,7 +239,7 @@ export class PeerTubeEmbed { | |||
239 | 239 | ||
240 | const config: ServerConfig = await configResponse.json() | 240 | const config: ServerConfig = await configResponse.json() |
241 | const description = config.tracker.enabled && this.warningTitle | 241 | const description = config.tracker.enabled && this.warningTitle |
242 | ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' | 242 | ? '<span class="text">' + this.player.localize('Watching this video may reveal your IP address to others.') + '</span>' |
243 | : undefined | 243 | : undefined |
244 | 244 | ||
245 | this.player.dock({ | 245 | this.player.dock({ |