Quellcode durchsuchen

Add about information in registration page

Chocobozzz vor 4 Jahren
Ursprung
Commit
421d935d25
22 geänderte Dateien mit 467 neuen und 172 gelöschten Zeilen
  1. 7 24
      client/src/app/+about/about-instance/about-instance.component.ts
  2. 93 54
      client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
  3. 12 7
      client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
  4. 10 5
      client/src/app/+signup/+register/register-step-user.component.html
  5. 15 1
      client/src/app/+signup/+register/register-step-user.component.ts
  6. 57 6
      client/src/app/+signup/+register/register.component.html
  7. 30 7
      client/src/app/+signup/+register/register.component.scss
  8. 38 2
      client/src/app/+signup/+register/register.component.ts
  9. 3 1
      client/src/app/+signup/+register/register.module.ts
  10. 5 4
      client/src/app/login/login.component.html
  11. 2 2
      client/src/app/shared/angular/peertube-template.directive.ts
  12. 9 2
      client/src/app/shared/forms/peertube-checkbox.component.html
  13. 20 4
      client/src/app/shared/forms/peertube-checkbox.component.ts
  14. 5 1
      client/src/app/shared/instance/instance-features-table.component.html
  15. 44 1
      client/src/app/shared/instance/instance.service.ts
  16. 19 9
      client/src/app/shared/misc/help.component.html
  17. 26 10
      client/src/app/shared/misc/help.component.ts
  18. 15 7
      client/src/app/shared/user-subscription/remote-subscribe.component.html
  19. 1 1
      client/src/app/shared/video/videos-selection.component.ts
  20. 38 16
      client/src/app/videos/+video-edit/shared/video-edit.component.html
  21. 8 4
      client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html
  22. 10 4
      client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html

+ 7 - 24
client/src/app/+about/about-instance/about-instance.component.ts

@@ -6,7 +6,6 @@ import { InstanceService } from '@app/shared/instance/instance.service'
 import { MarkdownService } from '@app/shared/renderer'
 import { forkJoin } from 'rxjs'
 import { first } from 'rxjs/operators'
-import { peertubeTranslate } from '@shared/models'
 
 @Component({
   selector: 'my-about-instance',
@@ -59,32 +58,16 @@ export class AboutInstanceComponent implements OnInit {
       this.serverService.videoLanguagesLoaded.pipe(first()),
       this.serverService.videoCategoriesLoaded.pipe(first())
     ]).subscribe(
-      async ([ res, translations ]) => {
-        this.shortDescription = res.instance.shortDescription
+      async ([ about, translations ]) => {
+        this.shortDescription = about.instance.shortDescription
 
-        this.maintenanceLifetime = res.instance.maintenanceLifetime
-        this.businessModel = res.instance.businessModel
+        this.maintenanceLifetime = about.instance.maintenanceLifetime
+        this.businessModel = about.instance.businessModel
 
-        for (const key of [ 'description', 'terms', 'codeOfConduct', 'moderationInformation', 'administrator' ]) {
-          this.html[ key ] = await this.markdownService.textMarkdownToHTML(res.instance[ key ])
-        }
+        this.html = await this.instanceService.buildHtml(about)
 
-        const languagesArray = this.serverService.getVideoLanguages()
-        const categoriesArray = this.serverService.getVideoCategories()
-
-        this.languages = res.instance.languages
-                            .map(l => {
-                              const languageObj = languagesArray.find(la => la.id === l)
-
-                              return peertubeTranslate(languageObj.label, translations)
-                            })
-
-        this.categories = res.instance.categories
-                             .map(c => {
-                               const categoryObj = categoriesArray.find(ca => ca.id === c)
-
-                               return peertubeTranslate(categoryObj.label, translations)
-                             })
+        this.languages = this.instanceService.buildTranslatedLanguages(about, translations)
+        this.categories = this.instanceService.buildTranslatedCategories(about, translations)
       },
 
       () => this.notifier.error(this.i18n('Cannot get about information from server'))

+ 93 - 54
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html

@@ -63,20 +63,30 @@
           <div i18n class="inner-form-title">Moderation & NSFW</div>
 
           <div class="form-group">
-            <my-peertube-checkbox
-              inputName="instanceIsNSFW" formControlName="isNSFW"
-              i18n-labelText labelText="This instance is dedicated to sensitive or NSFW content"
-              i18n-helpHtml helpHtml="Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br />
-              Moreover, the NSFW checkbox on video upload will be automatically checked by default."
-            ></my-peertube-checkbox>
+            <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW">
+              <ng-template ptTemplate="label">
+                <ng-container i18n>This instance is dedicated to sensitive or NSFW content</ng-container>
+              </ng-template>
+
+              <ng-template ptTemplate="help">
+                <ng-container i18n>
+                  Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br />
+                  Moreover, the NSFW checkbox on video upload will be automatically checked by default.
+                </ng-container>
+              </ng-template>
+            </my-peertube-checkbox>
           </div>
 
           <div class="form-group">
             <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
-            <my-help
-              helpType="custom" i18n-customHtml
-              customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."
-            ></my-help>
+
+            <my-help>
+              <ng-template ptTemplate="customHtml">
+                <ng-container i18n>
+                  With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video.
+                </ng-container>
+              </ng-template>
+            </my-help>
 
             <div class="peertube-select-container">
               <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy">
@@ -374,10 +384,13 @@
 
             <div class="form-group">
               <label i18n for="signupLimit">Your Twitter username</label>
-              <my-help
-                helpType="custom" i18n-customHtml
-                customHtml="Indicates the Twitter account for the website or platform on which the content was published."
-              ></my-help>
+
+              <my-help>
+                <ng-template ptTemplate="customHtml">
+                  <ng-container i18n>Indicates the Twitter account for the website or platform on which the content was published.</ng-container>
+                </ng-template>
+              </my-help>
+
               <input
                 type="text" id="servicesTwitterUsername"
                 formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }"
@@ -386,13 +399,21 @@
             </div>
 
             <div class="form-group">
-              <my-peertube-checkbox
-                inputName="servicesTwitterWhitelisted" formControlName="whitelisted"
-                i18n-labelText labelText="Instance whitelisted by Twitter"
-                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 />
-        If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br />
-        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."
-              ></my-peertube-checkbox>
+              <my-peertube-checkbox inputName="servicesTwitterWhitelisted" formControlName="whitelisted">
+                <ng-template ptTemplate="label">
+                  <ng-container i18n>Instance whitelisted by Twitter</ng-container>
+                </ng-template>
+
+                <ng-template ptTemplate="help">
+                  <ng-container i18n>
+                    If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
+                    If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br />
+                    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.
+                  </ng-container>
+                </ng-template>
+              </my-peertube-checkbox>
             </div>
 
           </ng-container>
@@ -408,11 +429,15 @@
 
         <ng-container formGroupName="transcoding">
           <div class="form-group">
-            <my-peertube-checkbox
-              inputName="transcodingEnabled" formControlName="enabled"
-              i18n-labelText labelText="Transcoding enabled"
-              i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!"
-            ></my-peertube-checkbox>
+            <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled">
+              <ng-template ptTemplate="label">
+                <ng-container i18n>Transcoding enabled</ng-container>
+              </ng-template>
+
+              <ng-template ptTemplate="help">
+                <ng-container i18n>If you disable transcoding, many videos from your users will not work!</ng-container>
+              </ng-template>
+            </my-peertube-checkbox>
           </div>
 
           <ng-container *ngIf="isTranscodingEnabled()">
@@ -421,16 +446,22 @@
               <my-peertube-checkbox
                 inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions"
                 i18n-labelText labelText="Allow additional extensions"
-                i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos"
-              ></my-peertube-checkbox>
+              >
+                <ng-template ptTemplate="help">
+                  <ng-container i18n>Allow your users to upload .mkv, .mov, .avi, .flv videos</ng-container>
+                </ng-template>
+              </my-peertube-checkbox>
             </div>
 
             <div class="form-group">
               <my-peertube-checkbox
                 inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles"
                 i18n-labelText labelText="Allow audio files upload"
-                i18n-helpHtml helpHtml="Allow your users to upload audio files that will be merged with the preview file on upload"
-              ></my-peertube-checkbox>
+              >
+                <ng-template ptTemplate="help">
+                  <ng-container i18n>Allow your users to upload audio files that will be merged with the preview file on upload</ng-container>
+                </ng-template>
+              </my-peertube-checkbox>
             </div>
 
             <div class="form-group">
@@ -460,10 +491,11 @@
         <div i18n class="inner-form-title">
           Cache
 
-          <my-help
-            helpType="custom" i18n-customHtml
-            customHtml="Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them."
-          ></my-help>
+          <my-help>
+            <ng-template ptTemplate="customHtml">
+              <ng-container i18n>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</ng-container>
+            </ng-template>
+          </my-help>
         </div>
 
         <ng-container formGroupName="cache">
@@ -492,38 +524,45 @@
           <ng-container formGroupName="customizations">
             <div class="form-group">
               <label i18n for="customizationJavascript">JavaScript</label>
-              <my-help
-                helpType="custom" i18n-customHtml
-                customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>"
-              ></my-help>
+              <my-help>
+                <ng-template ptTemplate="customHtml">
+                  <ng-container i18n>
+                    Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>
+                  </ng-container>
+                </ng-template>
+              </my-help>
+
               <textarea
                 id="customizationJavascript" formControlName="javascript"
                 [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }"
               ></textarea>
+
               <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div>
             </div>
 
             <div class="form-group">
               <label for="customizationCSS">CSS</label>
-              <my-help
-                  helpType="custom"
-                  i18n-customHtml
-                  customHtml="
+
+              <my-help>
+                <ng-template ptTemplate="customHtml">
+                  <ng-container i18n>
                     Write directly CSS code. Example:<br /><br />
-                    <pre>
-  #custom-css {{ '{' }}
-    color: red;
-  {{ '}' }}
-                    </pre>
+<pre>
+#custom-css {{ '{' }}
+  color: red;
+{{ '}' }}
+</pre>
 
                     Prepend with <em>#custom-css</em> to override styles. Example:<br /><br />
-                    <pre>
-  #custom-css .logged-in-email {{ '{' }}
-    color: red;
-  {{ '}' }}
-                    </pre>
-                  "
-              ></my-help>
+<pre>
+#custom-css .logged-in-email {{ '{' }}
+  color: red;
+{{ '}' }}
+</pre>
+                  </ng-container>
+                </ng-template>
+              </my-help>
+
               <textarea
                 id="customizationCSS" formControlName="css"
                 [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }"

+ 12 - 7
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html

@@ -1,10 +1,13 @@
 <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
   <div class="form-group">
     <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label>
-    <my-help
-      helpType="custom" i18n-customHtml
-      customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."
-    ></my-help>
+    <my-help>
+      <ng-template ptTemplate="customHtml">
+        <ng-container i18n>
+          With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video.
+        </ng-container>
+      </ng-template>
+    </my-help>
 
     <div class="peertube-select-container">
       <select id="nsfwPolicy" formControlName="nsfwPolicy">
@@ -17,9 +20,11 @@
 
   <div class="form-group">
     <label i18n for="videoLanguages">Only display videos in the following languages</label>
-    <my-help i18n-customHtml
-             customHtml="In Recently added, Trending, Local and Search pages"
-    ></my-help>
+    <my-help>
+      <ng-template ptTemplate="customHtml">
+        <ng-container i18n>In Recently added, Trending, Local and Search pages</ng-container>
+      </ng-template>
+    </my-help>
 
     <div>
       <p-multiSelect

+ 10 - 5
client/src/app/+signup/+register/register-step-user.component.html

@@ -60,11 +60,16 @@
   </div>
 
   <div class="form-group form-group-terms">
-    <my-peertube-checkbox
-      inputName="terms" formControlName="terms"
-      i18n-labelHtml
-      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"
-    ></my-peertube-checkbox>
+    <my-peertube-checkbox inputName="terms" formControlName="terms">
+      <ng-template ptTemplate="label">
+        <ng-container i18n>
+          I am at least 16 years old and agree
+          to the <a (click)="onTermsClick($event)" href='#'>Terms</a>
+          <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container>
+          of this instance
+        </ng-container>
+      </ng-template>
+    </my-peertube-checkbox>
 
     <div *ngIf="formErrors.terms" class="form-error">
       {{ formErrors.terms }}

+ 15 - 1
client/src/app/+signup/+register/register-step-user.component.ts

@@ -1,4 +1,4 @@
-import { Component, EventEmitter, OnInit, Output } from '@angular/core'
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
 import { AuthService } from '@app/core'
 import { FormReactive, UserService, UserValidatorsService } from '@app/shared'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
@@ -12,7 +12,11 @@ import { concat, of } from 'rxjs'
   styleUrls: [ './register.component.scss' ]
 })
 export class RegisterStepUserComponent extends FormReactive implements OnInit {
+  @Input() hasCodeOfConduct = false
+
   @Output() formBuilt = new EventEmitter<FormGroup>()
+  @Output() termsClick = new EventEmitter<void>()
+  @Output() codeOfConductClick = new EventEmitter<void>()
 
   constructor (
     protected formValidatorService: FormValidatorService,
@@ -45,6 +49,16 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
      .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
   }
 
+  onTermsClick (event: Event) {
+    event.preventDefault()
+    this.termsClick.emit()
+  }
+
+  onCodeOfConductClick (event: Event) {
+    event.preventDefault()
+    this.codeOfConductClick.emit()
+  }
+
   private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
     const username = this.form.value['username'] || ''
 

+ 57 - 6
client/src/app/+signup/+register/register.component.html

@@ -7,11 +7,15 @@
   <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success>
   <div *ngIf="info" class="alert alert-info">{{ info }}</div>
 
-  <div class="wrapper" *ngIf="!signupDone">
-    <div>
+  <div class="wrapper" [hidden]="signupDone">
+    <div class="register-form">
       <my-custom-stepper linear *ngIf="!signupDone">
         <cdk-step [stepControl]="formStepUser" i18n-label label="User information">
-          <my-register-step-user (formBuilt)="onUserFormBuilt($event)"></my-register-step-user>
+          <my-register-step-user
+            [hasCodeOfConduct]="!!aboutHtml.codeOfConduct"
+            (formBuilt)="onUserFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
+          >
+          </my-register-step-user>
 
           <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button>
         </cdk-step>
@@ -38,9 +42,56 @@
       </my-custom-stepper>
     </div>
 
-    <div>
-      <label i18n>Features found on this instance</label>
-      <my-instance-features-table></my-instance-features-table>
+    <div class="instance-information">
+      <ngb-accordion [closeOthers]="true" #accordion="ngbAccordion">
+        <ngb-panel id="instance-features" i18n-title title="Features found on this instance">
+          <ng-template ngbPanelContent>
+            <my-instance-features-table></my-instance-features-table>
+          </ng-template>
+        </ngb-panel>
+
+        <ng-container *ngIf="about">
+          <ngb-panel
+            *ngIf="aboutHtml.administrator || about.instance.maintenanceLifetime || about.instance.businessModel"
+            id="admin-sustainability" i18n-title title="Administrators & Sustainability"
+          >
+            <ng-template ngbPanelContent>
+              <div class="block">
+                <strong i18n>Who are we?</strong>
+                <div [innerHTML]="aboutHtml.administrator"></div>
+              </div>
+
+              <div class="block">
+                <strong i18n>How long do we plan to maintain this instance?</strong>
+                <div [innerHTML]="about.instance.maintenanceLifetime"></div>
+              </div>
+
+              <div class="block">
+                <strong i18n>How will we pay this instance?</strong>
+                <div [innerHTML]="about.instance.businessModel"></div>
+              </div>
+            </ng-template>
+          </ngb-panel>
+
+          <ngb-panel *ngIf="aboutHtml.moderationInformation" id="moderation-information" i18n-title title="Moderation information">
+            <ng-template ngbPanelContent>
+              <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div>
+            </ng-template>
+          </ngb-panel>
+
+          <ngb-panel *ngIf="aboutHtml.codeOfConduct" id="code-of-conduct" i18n-title title="Code of conduct">
+            <ng-template ngbPanelContent>
+              <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div>
+            </ng-template>
+          </ngb-panel>
+
+          <ngb-panel *ngIf="aboutHtml.terms" id="terms" i18n-title title="Terms">
+            <ng-template ngbPanelContent>
+              <div class="block" [innerHTML]="aboutHtml.terms"></div>
+            </ng-template>
+          </ngb-panel>
+        </ng-container>
+      </ngb-accordion>
     </div>
   </div>
 

+ 30 - 7
client/src/app/+signup/+register/register.component.scss

@@ -1,5 +1,9 @@
 @import '_variables';
 @import '_mixins';
+@import "./_bootstrap-variables";
+
+@import '~bootstrap/scss/functions';
+@import '~bootstrap/scss/variables';
 
 .alert {
   font-size: 15px;
@@ -13,7 +17,32 @@
 
   & > div {
     margin-bottom: 40px;
-    width: 450px;
+
+    &.register-form {
+      width: 450px;
+    }
+
+    &.instance-information {
+      width: 600px;
+      margin-bottom: 40px;
+
+      .block {
+        font-size: 15px;
+        margin-bottom: 15px;
+        padding: 0 $btn-padding-x;
+      }
+
+      @media screen and (max-width: 1500px) {
+        width: 450px;
+      }
+
+      ngb-accordion ::ng-deep {
+        .btn {
+          font-weight: $font-semibold !important;
+          color: var(--mainForegroundColor) !important;
+        }
+      }
+    }
 
     @media screen and (max-width: 500px) {
       width: auto;
@@ -21,12 +50,6 @@
   }
 }
 
-my-instance-features-table {
-  display: block;
-
-  margin-bottom: 40px;
-}
-
 .form-group-terms {
   margin: 30px 0;
 }

+ 38 - 2
client/src/app/+signup/+register/register.component.ts

@@ -1,21 +1,35 @@
-import { Component } from '@angular/core'
+import { Component, OnInit, ViewChild } from '@angular/core'
 import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
 import { UserService, UserValidatorsService } from '@app/shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { UserRegister } from '@shared/models/users/user-register.model'
 import { FormGroup } from '@angular/forms'
+import { About } from '@shared/models/server'
+import { InstanceService } from '@app/shared/instance/instance.service'
+import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
 
 @Component({
   selector: 'my-register',
   templateUrl: './register.component.html',
   styleUrls: [ './register.component.scss' ]
 })
-export class RegisterComponent {
+export class RegisterComponent implements OnInit {
+  @ViewChild('accordion', { static: true }) accordion: NgbAccordion
+
   info: string = null
   error: string = null
   success: string = null
   signupDone = false
 
+  about: About
+  aboutHtml = {
+    description: '',
+    terms: '',
+    codeOfConduct: '',
+    moderationInformation: '',
+    administrator: ''
+  }
+
   formStepUser: FormGroup
   formStepChannel: FormGroup
 
@@ -26,6 +40,7 @@ export class RegisterComponent {
     private userService: UserService,
     private serverService: ServerService,
     private redirectService: RedirectService,
+    private instanceService: InstanceService,
     private i18n: I18n
   ) {
   }
@@ -34,6 +49,19 @@ export class RegisterComponent {
     return this.serverService.getConfig().signup.requiresEmailVerification
   }
 
+  ngOnInit (): void {
+    this.instanceService.getAbout()
+      .subscribe(
+        async about => {
+          this.about = about
+
+          this.aboutHtml = await this.instanceService.buildHtml(about)
+        },
+
+        err => this.notifier.error(err.message)
+      )
+  }
+
   hasSameChannelAndAccountNames () {
     return this.getUsername() === this.getChannelName()
   }
@@ -58,6 +86,14 @@ export class RegisterComponent {
     this.formStepChannel = form
   }
 
+  onTermsClick () {
+    if (this.accordion) this.accordion.toggle('terms')
+  }
+
+  onCodeOfConductClick () {
+    if (this.accordion) this.accordion.toggle('code-of-conduct')
+  }
+
   signup () {
     this.error = null
 

+ 3 - 1
client/src/app/+signup/+register/register.module.ts

@@ -7,13 +7,15 @@ import { RegisterStepChannelComponent } from './register-step-channel.component'
 import { RegisterStepUserComponent } from './register-step-user.component'
 import { CustomStepperComponent } from './custom-stepper.component'
 import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module'
+import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
 
 @NgModule({
   imports: [
     RegisterRoutingModule,
     SharedModule,
     CdkStepperModule,
-    SignupSharedModule
+    SignupSharedModule,
+    NgbAccordionModule
   ],
 
   declarations: [

+ 5 - 4
client/src/app/login/login.component.html

@@ -23,10 +23,11 @@
           or create an account on another instance
         </a>
 
-        <my-help
-          *ngIf="signupAllowed === false" helpType="custom" i18n-customHtml
-          customHtml="User registration is not allowed on this instance, but you can register on many others!"
-        ></my-help>
+        <my-help *ngIf="signupAllowed === false">
+          <ng-template ptTemplate="customHtml">
+            <ng-container i18n>User registration is not allowed on this instance, but you can register on many others!</ng-container>
+          </ng-template>
+        </my-help>
       </div>
 
       <div *ngIf="formErrors.username" class="form-error">

+ 2 - 2
client/src/app/shared/angular/peertube-template.directive.ts

@@ -3,8 +3,8 @@ import { Directive, Input, TemplateRef } from '@angular/core'
 @Directive({
   selector: '[ptTemplate]'
 })
-export class PeerTubeTemplateDirective {
-  @Input('ptTemplate') name: string
+export class PeerTubeTemplateDirective <T extends string> {
+  @Input('ptTemplate') name: T
 
   constructor (public template: TemplateRef<any>) {
     // empty

+ 9 - 2
client/src/app/shared/forms/peertube-checkbox.component.html

@@ -3,8 +3,15 @@
     <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" />
     <span role="checkbox" [attr.aria-checked]="checked"></span>
     <span *ngIf="labelText">{{ labelText }}</span>
-    <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span>
+
+    <span *ngIf="labelTemplate">
+      <ng-container *ngTemplateOutlet="labelTemplate"></ng-container>
+    </span>
   </label>
 
-  <my-help *ngIf="helpHtml" [tooltipPlacement]="helpPlacement" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help>
+  <my-help *ngIf="helpTemplate" [tooltipPlacement]="helpPlacement" helpType="custom">
+    <ng-template ptTemplate="customHtml">
+      <ng-template *ngTemplateOutlet="helpTemplate"></ng-template>
+    </ng-template>
+  </my-help>
 </div>

+ 20 - 4
client/src/app/shared/forms/peertube-checkbox.component.ts

@@ -1,5 +1,6 @@
-import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core'
+import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core'
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
+import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
 
 @Component({
   selector: 'my-peertube-checkbox',
@@ -13,20 +14,35 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
     }
   ]
 })
-export class PeertubeCheckboxComponent implements ControlValueAccessor {
+export class PeertubeCheckboxComponent implements ControlValueAccessor, AfterContentInit {
   @Input() checked = false
   @Input() inputName: string
   @Input() labelText: string
-  @Input() labelHtml: string
-  @Input() helpHtml: string
   @Input() helpPlacement = 'top'
   @Input() disabled = false
 
+  @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'label' | 'help'>>
+
   // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836
   @Input() onPushWorkaround = false
 
+  labelTemplate: TemplateRef<any>
+  helpTemplate: TemplateRef<any>
+
   constructor (private cdr: ChangeDetectorRef) { }
 
+  ngAfterContentInit () {
+    {
+      const t = this.templates.find(t => t.name === 'label')
+      if (t) this.labelTemplate = t.template
+    }
+
+    {
+      const t = this.templates.find(t => t.name === 'help')
+      if (t) this.helpTemplate = t.template
+    }
+  }
+
   propagateChange = (_: any) => { /* empty */ }
 
   writeValue (checked: boolean) {

+ 5 - 1
client/src/app/shared/instance/instance-features-table.component.html

@@ -43,7 +43,11 @@
         <ng-container *ngIf="initialUserVideoQuota !== -1">
           {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container>
 
-          <my-help tooltipPlacement="auto" helpType="custom" [customHtml]="quotaHelpIndication"></my-help>
+          <my-help tooltipPlacement="auto" helpType="custom">
+            <ng-template ptTemplate="customHtml">
+              <div [innerHTML]="quotaHelpIndication"></div>
+            </ng-template>
+          </my-help>
         </ng-container>
 
         <ng-container i18n *ngIf="initialUserVideoQuota === -1">

+ 44 - 1
client/src/app/shared/instance/instance.service.ts

@@ -4,6 +4,9 @@ import { Injectable } from '@angular/core'
 import { environment } from '../../../environments/environment'
 import { RestExtractor, RestService } from '../rest'
 import { About } from '../../../../../shared/models/server'
+import { MarkdownService } from '@app/shared/renderer'
+import { peertubeTranslate } from '@shared/models'
+import { ServerService } from '@app/core'
 
 @Injectable()
 export class InstanceService {
@@ -13,7 +16,9 @@ export class InstanceService {
   constructor (
     private authHttp: HttpClient,
     private restService: RestService,
-    private restExtractor: RestExtractor
+    private restExtractor: RestExtractor,
+    private markdownService: MarkdownService,
+    private serverService: ServerService
   ) {
   }
 
@@ -34,4 +39,42 @@ export class InstanceService {
                .pipe(catchError(res => this.restExtractor.handleError(res)))
 
   }
+
+  async buildHtml (about: About) {
+    const html = {
+      description: '',
+      terms: '',
+      codeOfConduct: '',
+      moderationInformation: '',
+      administrator: ''
+    }
+
+    for (const key of [ 'description', 'terms', 'codeOfConduct', 'moderationInformation', 'administrator' ]) {
+      html[ key ] = await this.markdownService.textMarkdownToHTML(about.instance[ key ])
+    }
+
+    return html
+  }
+
+  buildTranslatedLanguages (about: About, translations: any) {
+    const languagesArray = this.serverService.getVideoLanguages()
+
+    return about.instance.languages
+                .map(l => {
+                  const languageObj = languagesArray.find(la => la.id === l)
+
+                  return peertubeTranslate(languageObj.label, translations)
+                })
+  }
+
+  buildTranslatedCategories (about: About, translations: any) {
+    const categoriesArray = this.serverService.getVideoCategories()
+
+    return about.instance.categories
+                .map(c => {
+                  const categoryObj = categoriesArray.find(ca => ca.id === c)
+
+                  return peertubeTranslate(categoryObj.label, translations)
+                })
+  }
 }

+ 19 - 9
client/src/app/shared/misc/help.component.html

@@ -1,15 +1,25 @@
 <ng-template #tooltipTemplate>
-  <ng-template [ngIf]="preHtml">
-    <p [innerHTML]="preHtml"></p>
-    <br />
-  </ng-template>
+  <p *ngIf="preHtmlTemplate">
+    <ng-template *ngTemplateOutlet="preHtmlTemplate"></ng-template>
+  </p>
 
-  <p [innerHTML]="mainHtml"></p>
+  <ng-container *ngIf="preHtmlTemplate && (customHtmlTemplate || mainHtml || postHtmlTemplate)">
+    <br /><br />
+  </ng-container>
 
-  <ng-template [ngIf]="postHtml">
-    <br />
-    <p [innerHTML]="postHtml"></p>
-  </ng-template>
+  <p *ngIf="customHtmlTemplate">
+    <ng-template *ngTemplateOutlet="customHtmlTemplate"></ng-template>
+  </p>
+
+  <p *ngIf="mainHtml" [innerHTML]="mainHtml"></p>
+
+  <ng-container *ngIf="(customHtmlTemplate || mainHtml) && postHtmlTemplate">
+    <br /><br />
+  </ng-container>
+
+  <p *ngIf="postHtmlTemplate">
+    <ng-template *ngTemplateOutlet="postHtmlTemplate"></ng-template>
+  </p>
 </ng-template>
 
 <span

+ 26 - 10
client/src/app/shared/misc/help.component.ts

@@ -1,6 +1,7 @@
-import { Component, Input, OnChanges, OnInit } from '@angular/core'
+import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { MarkdownService } from '@app/shared/renderer'
+import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
 
 @Component({
   selector: 'my-help',
@@ -8,22 +9,42 @@ import { MarkdownService } from '@app/shared/renderer'
   templateUrl: './help.component.html'
 })
 
-export class HelpComponent implements OnInit, OnChanges {
-  @Input() preHtml = ''
-  @Input() postHtml = ''
-  @Input() customHtml = ''
+export class HelpComponent implements OnInit, OnChanges, AfterContentInit {
   @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom'
   @Input() tooltipPlacement = 'right'
 
+  @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'preHtml' | 'customHtml' | 'postHtml'>>
+
   isPopoverOpened = false
   mainHtml = ''
 
+  preHtmlTemplate: TemplateRef<any>
+  customHtmlTemplate: TemplateRef<any>
+  postHtmlTemplate: TemplateRef<any>
+
   constructor (private i18n: I18n) { }
 
   ngOnInit () {
     this.init()
   }
 
+  ngAfterContentInit () {
+    {
+      const t = this.templates.find(t => t.name === 'preHtml')
+      if (t) this.preHtmlTemplate = t.template
+    }
+
+    {
+      const t = this.templates.find(t => t.name === 'customHtml')
+      if (t) this.customHtmlTemplate = t.template
+    }
+
+    {
+      const t = this.templates.find(t => t.name === 'postHtml')
+      if (t) this.postHtmlTemplate = t.template
+    }
+  }
+
   ngOnChanges () {
     this.init()
   }
@@ -37,11 +58,6 @@ export class HelpComponent implements OnInit, OnChanges {
   }
 
   private init () {
-    if (this.helpType === 'custom') {
-      this.mainHtml = this.customHtml
-      return
-    }
-
     if (this.helpType === 'markdownText') {
       this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES)
       return

+ 15 - 7
client/src/app/shared/user-subscription/remote-subscribe.component.html

@@ -12,13 +12,21 @@
     <span *ngIf="interact">Remote interact</span>
   </button>
 
-  <my-help *ngIf="!interact && showHelp"
-           helpType="custom"
-           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.">
+  <my-help *ngIf="!interact && showHelp">
+    <ng-template ptTemplate="customHtml">
+      <ng-container i18n>
+        You can subscribe to the channel via any ActivityPub-capable fediverse instance.<br /><br />
+        For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there.
+      </ng-container>
+    </ng-template>
   </my-help>
 
-  <my-help *ngIf="showHelp && interact"
-           helpType="custom"
-           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.">
+  <my-help *ngIf="showHelp && interact">
+    <ng-template ptTemplate="customHtml">
+      <ng-container i18n>
+        You can interact with this via any ActivityPub-capable fediverse instance.<br /><br />
+        For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there.
+      </ng-container>
+    </ng-template>
   </my-help>
-</form>
+</form>

+ 1 - 1
client/src/app/shared/video/videos-selection.component.ts

@@ -35,7 +35,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
   @Input() titlePage: string
   @Input() miniatureDisplayOptions: MiniatureDisplayOptions
   @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>>
-  @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective>
+  @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'rowButtons' | 'globalButtons'>>
 
   @Output() selectionChange = new EventEmitter<SelectionType>()
   @Output() videosModelChange = new EventEmitter<Video[]>()

+ 38 - 16
client/src/app/videos/+video-edit/shared/video-edit.component.html

@@ -15,7 +15,16 @@
 
             <div class="form-group">
               <label i18n class="label-tags">Tags</label>
-              <my-help i18n-preHtml preHtml="Tags could be used to suggest relevant recommendations.</br>Press Enter to add a new tag."></my-help>
+
+              <my-help>
+                <ng-template ptTemplate="customHtml">
+                  <ng-container i18n>
+                    Tags could be used to suggest relevant recommendations. <br />
+                    Press Enter to add a new tag.
+                  </ng-container>
+                </ng-template>
+              </my-help>
+
               <tag-input
                 [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
                 i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag"
@@ -25,7 +34,15 @@
 
             <div class="form-group">
               <label i18n for="description">Description</label>
-              <my-help helpType="markdownText" i18n-preHtml preHtml="Video descriptions are truncated by default and require manual action to expand them."></my-help>
+
+              <my-help helpType="markdownText">
+                <ng-template ptTemplate="preHtml">
+                  <ng-container i18n>
+                    Video descriptions are truncated by default and require manual action to expand them.
+                  </ng-container>
+                </ng-template>
+              </my-help>
+
               <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea>
 
               <div *ngIf="formErrors.description" class="form-error">
@@ -114,20 +131,25 @@
               </div>
             </div>
 
-            <my-peertube-checkbox
-              inputName="nsfw" formControlName="nsfw"
-              i18n-labelText labelText="This video contains mature or explicit content"
-              i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default."
-              helpPlacement="bottom-right"
-            ></my-peertube-checkbox>
-
-            <my-peertube-checkbox
-              *ngIf="waitTranscodingEnabled"
-              inputName="waitTranscoding" formControlName="waitTranscoding"
-              i18n-labelText labelText="Wait transcoding before publishing the video"
-              i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
-              helpPlacement="bottom-right"
-            ></my-peertube-checkbox>
+            <my-peertube-checkbox inputName="nsfw" formControlName="nsfw" helpPlacement="bottom-right">
+              <ng-template ptTemplate="label">
+                <ng-container i18n>This video contains mature or explicit content</ng-container>
+              </ng-template>
+
+              <ng-template ptTemplate="help">
+                <ng-container i18n>Some instances do not list videos containing mature or explicit content by default.</ng-container>
+              </ng-template>
+            </my-peertube-checkbox>
+
+            <my-peertube-checkbox *ngIf="waitTranscodingEnabled" inputName="waitTranscoding" formControlName="waitTranscoding" helpPlacement="bottom-right">
+              <ng-template ptTemplate="label">
+                <ng-container i18n>Wait transcoding before publishing the video</ng-container>
+              </ng-template>
+
+              <ng-template ptTemplate="help">
+                <ng-container i18n>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</ng-container>
+              </ng-template>
+            </my-peertube-checkbox>
 
           </div>
         </div>

+ 8 - 4
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html

@@ -12,10 +12,14 @@
 
     <div class="form-group form-group-magnet-uri">
       <label i18n for="magnetUri">Paste magnet URI</label>
-      <my-help
-        helpType="custom" i18n-customHtml
-        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."
-      ></my-help>
+      <my-help>
+        <ng-template ptTemplate="customHtml">
+          <ng-container i18n>
+            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.
+          </ng-container>
+        </ng-template>
+      </my-help>
 
       <input type="text" id="magnetUri" [(ngModel)]="magnetUri" />
     </div>

+ 10 - 4
client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html

@@ -4,10 +4,16 @@
 
     <div class="form-group">
       <label i18n for="targetUrl">URL</label>
-      <my-help
-        helpType="custom" i18n-customHtml
-        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."
-      ></my-help>
+
+      <my-help>
+        <ng-template ptTemplate="customHtml">
+          <ng-container i18n>
+            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.
+          </ng-container>
+        </ng-template>
+      </my-help>
 
       <input type="text" id="targetUrl" [(ngModel)]="targetUrl" />
     </div>