Add more attributes to about page
authorChocobozzz <me@florianbigard.com>
Fri, 23 Aug 2019 13:23:27 +0000 (15:23 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Thu, 5 Sep 2019 08:17:02 +0000 (10:17 +0200)
18 files changed:
client/src/app/+about/about-instance/about-instance.component.html
client/src/app/+about/about-instance/about-instance.component.scss
client/src/app/+about/about-instance/about-instance.component.ts
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
client/src/app/+my-account/my-account.module.ts
client/src/app/shared/shared.module.ts
config/default.yaml
server/controllers/api/config.ts
server/initializers/config.ts
server/tests/api/check-params/config.ts
server/tests/api/server/config.ts
shared/extra-utils/server/config.ts
shared/models/server/about.model.ts
shared/models/server/custom-config.model.ts

index 7c27ec7607f5cd277602df33322280228898be26..0fd3626b708bfb1c4a3c3b08ecd90b1f9b8143b1 100644 (file)
@@ -1,7 +1,8 @@
 <div class="row">
   <div class="col-md-12 col-xl-6">
+
     <div class="about-instance-title">
-      <div i18n>About {{ instanceName }} instance</div>
+      <div i18n class="title">About {{ instanceName }} instance</div>
 
       <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div>
     </div>
       <div *ngIf="isNSFW" class="dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div>
     </div>
 
-    <div class="description">
+    <div class="middle-title" *ngIf="html.administrator || maintenanceLifetime || businessModel">
+      Administrators & sustainability
+    </div>
+
+    <div class="block administrator" *ngIf="html.administrator">
+      <div i18n class="section-title">Instance administrators</div>
+
+      <div [innerHTML]="html.administrator"></div>
+    </div>
+
+    <div class="block maintenance-lifetime" *ngIf="maintenanceLifetime">
+      <div i18n class="section-title">Maintenance lifetime</div>
+
+      <p>{{ maintenanceLifetime }}</p>
+    </div>
+
+    <div class="block business-model" *ngIf="businessModel">
+      <div i18n class="section-title">Business model</div>
+
+      <p>{{ businessModel }}</p>
+    </div>
+
+    <div class="middle-title" *ngIf="html.description">
+      Information
+    </div>
+
+    <div class="block description">
       <div i18n class="section-title">Description</div>
 
-      <div [innerHTML]="descriptionHTML"></div>
+      <div [innerHTML]="html.description"></div>
+    </div>
+
+    <div class="middle-title" *ngIf="html.moderationInformation || html.codeOfConduct || html.terms">
+      Moderation
+    </div>
+
+    <div class="block moderation-information" *ngIf="html.moderationInformation">
+      <div i18n class="section-title">Moderation information</div>
+
+      <div [innerHTML]="html.moderationInformation"></div>
+    </div>
+
+    <div class="block code-of-conduct" *ngIf="html.codeOfConduct">
+      <div i18n class="section-title">Code of conduct</div>
+
+      <div [innerHTML]="html.codeOfConduct"></div>
     </div>
 
-    <div class="terms" id="terms-section">
+    <div class="block terms" id="terms-section">
       <div i18n class="section-title">Terms</div>
 
-      <div [innerHTML]="termsHTML"></div>
+      <div [innerHTML]="html.terms"></div>
     </div>
   </div>
 
index 0296ae8e9082f39bde640b576934e2fd176f5727..0585ad5f3bd399aecaeeaf0e0419e2557faafca4 100644 (file)
@@ -5,13 +5,13 @@
   display: flex;
   justify-content: space-between;
 
-  & > div {
+  .title {
     font-size: 20px;
-    font-weight: bold;
     margin-bottom: 15px;
+    font-weight: $font-semibold;
   }
 
-  & > .contact-admin {
+  .contact-admin {
     @include peertube-button;
     @include orange-button;
 
 
 .section-title {
   font-weight: $font-semibold;
-  font-size: 20px;
+  font-size: 16px;
   margin-bottom: 5px;
+  display: flex;
+  align-items: center;
+}
+
+.middle-title {
+  @include in-content-small-title;
+
+  margin-top: 45px;
+  margin-bottom: 25px;
 }
 
-.short-description, .description, .terms, .signup {
+.block {
   margin-bottom: 30px;
 }
 
index a5204de275291d87bbfb07e06bbd1ff9c21d6773..b85a6be948bbbb8f1d8af8734bdd87c5a9a64455 100644 (file)
@@ -14,8 +14,20 @@ export class AboutInstanceComponent implements OnInit {
   @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent
 
   shortDescription = ''
-  descriptionHTML = ''
-  termsHTML = ''
+
+  html = {
+    description: '',
+    terms: '',
+    codeOfConduct: '',
+    moderationInformation: '',
+    administrator: ''
+  }
+
+  maintenanceLifetime = ''
+  businessModel = ''
+
+  languages: string[] = []
+  categories: number[] = []
 
   constructor (
     private notifier: Notifier,
@@ -43,8 +55,15 @@ export class AboutInstanceComponent implements OnInit {
         async res => {
           this.shortDescription = res.instance.shortDescription
 
-          this.descriptionHTML = await this.markdownService.textMarkdownToHTML(res.instance.description)
-          this.termsHTML = await this.markdownService.textMarkdownToHTML(res.instance.terms)
+          this.maintenanceLifetime = res.instance.maintenanceLifetime
+          this.businessModel = res.instance.businessModel
+
+          for (const key of [ 'description', 'terms', 'codeOfConduct', 'moderationInformation', 'administrator' ]) {
+            this.html[key] = await this.markdownService.textMarkdownToHTML(res.instance[key])
+          }
+
+          this.languages = res.instance.languages
+          this.categories = res.instance.categories
         },
 
         () => this.notifier.error(this.i18n('Cannot get about information from server'))
index ec6f879d7046a262e65278a607f677c295aaab40..50df8a8ac4fd65c02ff04c797588f2c705932ac9 100644 (file)
@@ -2,12 +2,13 @@
 
   <ngb-tabset class="root-tabset bootstrap">
 
-    <ngb-tab i18n-title title="Basic configuration">
+    <ngb-tab i18n-title title="Instance information">
       <ng-template ngbTabContent>
 
-        <div i18n class="inner-form-title">Instance</div>
-
         <ng-container formGroupName="instance">
+
+          <div i18n class="inner-form-title">Instance</div>
+
           <div class="form-group">
             <label i18n for="instanceName">Name</label>
             <input
           </div>
 
           <div class="form-group">
-            <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
-            <my-markdown-textarea
-              id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true"
-              [ngClass]="{ 'input-error': formErrors['instance.terms'] }"
-            ></my-markdown-textarea>
-            <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div>
+            <label i18n for="instanceCategories">Main instance categories</label>
+
+            <div>
+              <p-multiSelect
+                inputId="instanceCategories" [options]="categoryItems" formControlName="categories" showToggleAll="false"
+                [defaultLabel]="getDefaultCategoryLabel()" [selectedItemsLabel]="getSelectedCategoryLabel()"
+                emptyFilterMessage="No results found" i18n-emptyFilterMessage
+              ></p-multiSelect>
+            </div>
           </div>
 
+          <div class="form-group">
+            <label i18n for="instanceLanguages">Main languages you/your moderators speak</label>
+
+            <div>
+              <p-multiSelect
+                inputId="instanceLanguages" [options]="languageItems" formControlName="languages" showToggleAll="false"
+                [defaultLabel]="getDefaultLanguageLabel()" [selectedItemsLabel]="getSelectedLanguageLabel()"
+                emptyFilterMessage="No results found" i18n-emptyFilterMessage
+              ></p-multiSelect>
+            </div>
+          </div>
+
+          <div i18n class="inner-form-title">Moderation & NSFW</div>
+
           <div class="form-group">
             <my-peertube-checkbox
               inputName="instanceIsNSFW" formControlName="isNSFW"
-              i18n-labelText labelText="Dedicated to sensitive or NSFW content"
+              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>
           </div>
 
-          <div class="form-group">
-            <label i18n for="instanceDefaultClientRoute">Default client route</label>
-            <div class="peertube-select-container">
-              <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute">
-                <option i18n value="/videos/overview">Videos Overview</option>
-                <option i18n value="/videos/trending">Videos Trending</option>
-                <option i18n value="/videos/recently-added">Videos Recently Added</option>
-                <option i18n value="/videos/local">Local videos</option>
-              </select>
-            </div>
-            <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div>
-          </div>
-
           <div class="form-group">
             <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
             <my-help
             </div>
             <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div>
           </div>
+
+          <div class="form-group">
+            <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
+            <my-markdown-textarea
+              id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true"
+              [ngClass]="{ 'input-error': formErrors['instance.terms'] }"
+            ></my-markdown-textarea>
+            <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div>
+          </div>
+
+          <div class="form-group">
+            <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help>
+            <my-markdown-textarea
+              id="instanceCodeOfConduct" formControlName="codeOfConduct" textareaWidth="500px" [previewColumn]="true"
+              [ngClass]="{ 'input-error': formErrors['instance.codeOfConduct'] }"
+            ></my-markdown-textarea>
+            <div *ngIf="formErrors.instance.codeOfConduct" class="form-error">{{ formErrors.instance.codeOfConduct }}</div>
+          </div>
+
+          <div class="form-group">
+            <label i18n for="instanceModerationInformation">Moderation information</label><my-help helpType="markdownText"></my-help>
+            <div class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div>
+
+            <my-markdown-textarea
+              id="instanceModerationInformation" formControlName="moderationInformation" textareaWidth="500px" [previewColumn]="true"
+              [ngClass]="{ 'input-error': formErrors['instance.moderationInformation'] }"
+            ></my-markdown-textarea>
+            <div *ngIf="formErrors.instance.moderationInformation" class="form-error">{{ formErrors.instance.moderationInformation }}</div>
+          </div>
+
+          <div i18n class="inner-form-title">You and your instance</div>
+
+          <div class="form-group">
+            <label i18n for="instanceAdministrator">Who is behind the instance? </label>
+            <div class="label-small-info">A single person? A non profit? A company?</div>
+
+            <textarea
+              id="instanceAdministrator" formControlName="administrator"
+              [ngClass]="{ 'input-error': formErrors['instance.administrator'] }"
+            ></textarea>
+            <div *ngIf="formErrors.instance.administrator" class="form-error">{{ formErrors.instance.administrator }}</div>
+          </div>
+
+          <div class="form-group">
+            <label i18n for="instanceMaintenanceLifetime">How long do you plan to maintain this instance?</label>
+            <div class="label-small-info">It's important to know for users who want to register on your instance</div>
+
+            <textarea
+              id="instanceMaintenanceLifetime" formControlName="maintenanceLifetime"
+              [ngClass]="{ 'input-error': formErrors['instance.maintenanceLifetime'] }"
+            ></textarea>
+            <div *ngIf="formErrors.instance.maintenanceLifetime" class="form-error">{{ formErrors.instance.maintenanceLifetime }}</div>
+          </div>
+
+          <div class="form-group">
+            <label i18n for="instanceBusinessModel">How will you pay the PeerTube instance server?</label>
+            <div class="label-small-info">With you own funds? With users donations? Advertising?</div>
+
+            <textarea
+              id="instanceBusinessModel" formControlName="businessModel"
+              [ngClass]="{ 'input-error': formErrors['instance.businessModel'] }"
+            ></textarea>
+            <div *ngIf="formErrors.instance.businessModel" class="form-error">{{ formErrors.instance.businessModel }}</div>
+          </div>
+
         </ng-container>
+      </ng-template>
+    </ngb-tab>
 
+    <ngb-tab i18n-title title="Basic configuration">
+      <ng-template ngbTabContent>
 
-        <div i18n class="inner-form-title">Theme</div>
+        <div i18n class="inner-form-title">Theme & Default route</div>
 
         <ng-container formGroupName="theme">
           <div class="form-group">
         </ng-container>
 
 
+        <div class="form-group" formGroupName="instance">
+          <label i18n for="instanceDefaultClientRoute">Default client route</label>
+          <div class="peertube-select-container">
+            <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute">
+              <option i18n value="/videos/overview">Videos Discover</option>
+              <option i18n value="/videos/trending">Videos Trending</option>
+              <option i18n value="/videos/recently-added">Videos Recently Added</option>
+              <option i18n value="/videos/local">Local videos</option>
+            </select>
+          </div>
+          <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div>
+        </div>
+
         <div i18n class="inner-form-title">Signup</div>
 
         <ng-container formGroupName="signup">
index c90bd514155238bbe6ab7f93e927e87d8d4da269..68f1b01b7eff659c913881c0c073d42550e0c65c 100644 (file)
@@ -1,6 +1,10 @@
 @import '_variables';
 @import '_mixins';
 
+.form-group {
+  margin-bottom: 25px;
+}
+
 input[type=text] {
   @include peertube-input-text(340px);
   display: block;
@@ -44,3 +48,8 @@ textarea {
     height: 100px;
   }
 }
+
+.label-small-info {
+  font-style: italic;
+  margin-bottom: 10px;
+}
index d5110456908c5c39ac42cc853645b113d6fb7e38..3119ab0408fb569c353f53024cfdbe481f8a0e0f 100644 (file)
@@ -6,6 +6,9 @@ import { Notifier } from '@app/core'
 import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
+import { SelectItem } from 'primeng/api'
+import { forkJoin } from 'rxjs'
+import { first } from 'rxjs/operators'
 
 @Component({
   selector: 'my-edit-custom-config',
@@ -18,6 +21,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
   resolutions: { id: string, label: string }[] = []
   transcodingThreadOptions: { label: string, value: number }[] = []
 
+  languageItems: SelectItem[] = []
+  categoryItems: SelectItem[] = []
+
   constructor (
     protected formValidatorService: FormValidatorService,
     private customConfigValidatorsService: CustomConfigValidatorsService,
@@ -88,10 +94,22 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
         name: this.customConfigValidatorsService.INSTANCE_NAME,
         shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION,
         description: null,
-        terms: null,
-        defaultClientRoute: null,
+
         isNSFW: false,
         defaultNSFWPolicy: null,
+
+        terms: null,
+        codeOfConduct: null,
+        moderationInformation: null,
+        administrator: null,
+        maintenanceLifetime: null,
+        businessModel: null,
+
+        categories: null,
+        languages: null,
+
+        defaultClientRoute: null,
+
         customizations: {
           javascript: null,
           css: null
@@ -184,18 +202,27 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
 
     this.buildForm(formGroupData)
 
-    this.configService.getCustomConfig()
-      .subscribe(
-        res => {
-          this.customConfig = res
+    forkJoin([
+      this.configService.getCustomConfig(),
+      this.serverService.videoLanguagesLoaded.pipe(first()), // First so the observable completes
+      this.serverService.videoCategoriesLoaded.pipe(first())
+    ]).subscribe(
+      ([ config ]) => {
+        this.customConfig = config
 
-          this.updateForm()
-          // Force form validation
-          this.forceCheck()
-        },
+        const languages = this.serverService.getVideoLanguages()
+        this.languageItems = languages.map(l => ({ label: l.label, value: l.id }))
 
-        err => this.notifier.error(err.message)
-      )
+        const categories = this.serverService.getVideoCategories()
+        this.categoryItems = categories.map(l => ({ label: l.label, value: l.id }))
+
+        this.updateForm()
+        // Force form validation
+        this.forceCheck()
+      },
+
+      err => this.notifier.error(err.message)
+    )
   }
 
   isTranscodingEnabled () {
@@ -224,8 +251,23 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
       )
   }
 
+  getSelectedLanguageLabel () {
+    return this.i18n('{{\'{0} languages selected')
+  }
+
+  getDefaultLanguageLabel () {
+    return this.i18n('No language')
+  }
+
+  getSelectedCategoryLabel () {
+    return this.i18n('{{\'{0} categories selected')
+  }
+
+  getDefaultCategoryLabel () {
+    return this.i18n('No category')
+  }
+
   private updateForm () {
     this.form.patchValue(this.customConfig)
   }
-
 }
index 2796dd2dbb75d1af9e17c90a2bdd1f1ca4f59bc8..caa032149c8d04ca6b6649ad4437bdbeaa69c8b5 100644 (file)
@@ -23,7 +23,7 @@
 
     <div>
       <p-multiSelect
-        [options]="languageItems" formControlName="videoLanguages" showToggleAll="true"
+        inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" showToggleAll="true"
         [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()"
         emptyFilterMessage="No results found" i18n-emptyFilterMessage
       ></p-multiSelect>
index 77febf1796e4796d80b081b6e9791c0b740e43c7..4fb82808285a5012959e4a0666b4514bfab68fa8 100644 (file)
@@ -5,9 +5,9 @@ import { AuthService } from '../../../core'
 import { FormReactive, User, UserService } from '../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
-import { Subject } from 'rxjs'
+import { forkJoin, Subject } from 'rxjs'
 import { SelectItem } from 'primeng/api'
-import { switchMap } from 'rxjs/operators'
+import { first } from 'rxjs/operators'
 
 @Component({
   selector: 'my-account-video-settings',
@@ -39,30 +39,31 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
       videoLanguages: null
     })
 
-    this.serverService.videoLanguagesLoaded
-        .pipe(switchMap(() => this.userInformationLoaded))
-        .subscribe(() => {
-          const languages = this.serverService.getVideoLanguages()
-
-          this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ]
-          this.languageItems = this.languageItems
-                                   .concat(languages.map(l => ({ label: l.label, value: l.id })))
-
-          const videoLanguages = this.user.videoLanguages
-            ? this.user.videoLanguages
-            : this.languageItems.map(l => l.value)
-
-          this.form.patchValue({
-            nsfwPolicy: this.user.nsfwPolicy,
-            webTorrentEnabled: this.user.webTorrentEnabled,
-            autoPlayVideo: this.user.autoPlayVideo === true,
-            videoLanguages
-          })
-        })
+    forkJoin([
+      this.serverService.videoLanguagesLoaded.pipe(first()),
+      this.userInformationLoaded.pipe(first())
+    ]).subscribe(() => {
+      const languages = this.serverService.getVideoLanguages()
+
+      this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ]
+      this.languageItems = this.languageItems
+                               .concat(languages.map(l => ({ label: l.label, value: l.id })))
+
+      const videoLanguages = this.user.videoLanguages
+        ? this.user.videoLanguages
+        : this.languageItems.map(l => l.value)
+
+      this.form.patchValue({
+        nsfwPolicy: this.user.nsfwPolicy,
+        webTorrentEnabled: this.user.webTorrentEnabled,
+        autoPlayVideo: this.user.autoPlayVideo === true,
+        videoLanguages
+      })
+    })
   }
 
   updateDetails () {
-    const nsfwPolicy = this.form.value['nsfwPolicy']
+    const nsfwPolicy = this.form.value[ 'nsfwPolicy' ]
     const webTorrentEnabled = this.form.value['webTorrentEnabled']
     const autoPlayVideo = this.form.value['autoPlayVideo']
 
index 571f46de99eba5335b2298d054914a3191c5455a..6cf1499d336583373624dc3afe41822d418961a0 100644 (file)
@@ -37,7 +37,6 @@ import {
 } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
 import { DragDropModule } from '@angular/cdk/drag-drop'
 import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email'
-import { MultiSelectModule } from 'primeng/multiselect'
 import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
 
 @NgModule({
@@ -48,8 +47,7 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account
     SharedModule,
     TableModule,
     InputSwitchModule,
-    DragDropModule,
-    MultiSelectModule
+    DragDropModule
   ],
 
   declarations: [
index eb57a2fff0fc6db07c5367d9006bd8feea2399fa..d71f6357bdeb66cc3f86b01f54f5ef1c5505cdae 100644 (file)
@@ -6,10 +6,8 @@ import { RouterModule } from '@angular/router'
 import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
 import { HelpComponent } from '@app/shared/misc/help.component'
 import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
-
 import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
 import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
-
 import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
 import { ButtonComponent } from './buttons/button.component'
 import { DeleteButtonComponent } from './buttons/delete-button.component'
@@ -93,6 +91,7 @@ import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.
 import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
 import { ClipboardModule } from 'ngx-clipboard'
 import { FollowService } from '@app/shared/instance/follow.service'
+import { MultiSelectModule } from 'primeng/multiselect'
 
 @NgModule({
   imports: [
@@ -113,7 +112,8 @@ import { FollowService } from '@app/shared/instance/follow.service'
 
     PrimeSharedModule,
     InputMaskModule,
-    NgPipesModule
+    NgPipesModule,
+    MultiSelectModule
   ],
 
   declarations: [
@@ -186,6 +186,7 @@ import { FollowService } from '@app/shared/instance/follow.service'
     InputMaskModule,
     BytesPipe,
     KeysPipe,
+    MultiSelectModule,
 
     LoaderComponent,
     SmallLoaderComponent,
index 5a935fede090d30471d5c847b1594d7ce4cb5d02..f84ecfcf9719344a91d87f53e8f0c7d673cef08a 100644 (file)
@@ -238,7 +238,53 @@ instance:
   short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.'
   description: 'Welcome to this PeerTube instance!' # Support markdown
   terms: 'No terms for now.' # Support markdown
+  code_of_conduct: '' # Supports markdown
+
+  # Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc
+  moderation_information: '' # Supports markdown
+
+  # Who is behind the instance? A single person? A non profit?
+  administrator: ''
+
+  # How long do you plan to maintain this instance?
+  maintenance_lifetime: ''
+
+  # How will you pay the PeerTube instance server? With you own funds? With users donations? Advertising?
+  business_model: ''
+
+  # What are the main languages of your instance? To interact with your users for example
+  # Uncomment or add the languages you want
+  # List of supported languages: https://peertube.cpy.re/api/v1/videos/languages
+  languages:
+#    - en
+#    - es
+#    - fr
+
+  # You can specify the main categories of your instance (dedicated to music, gaming or politics etc)
+  # Uncomment or add the category ids you want
+  # List of supported categories: https://peertube.cpy.re/api/v1/videos/categories
+  categories:
+#    - 1  # Music
+#    - 2  # Films
+#    - 3  # Vehicles
+#    - 4  # Art
+#    - 5  # Sports
+#    - 6  # Travels
+#    - 7  # Gaming
+#    - 8  # People
+#    - 9  # Comedy
+#    - 10 # Entertainment
+#    - 11 # News & Politics
+#    - 12 # How To
+#    - 13 # Education
+#    - 14 # Activism
+#    - 15 # Science & Technology
+#    - 16 # Animals
+#    - 17 # Kids
+#    - 18 # Food
+
   default_client_route: '/videos/trending'
+
   # Whether or not the instance is dedicated to NSFW content
   # Enabling it will allow other administrators to know that you are mainly federating sensitive content
   # Moreover, the NSFW checkbox on video upload will be automatically checked by default
@@ -246,6 +292,7 @@ instance:
   # By default, "do_not_list" or "blur" or "display" NSFW videos
   # Could be overridden per user with a setting
   default_nsfw_policy: 'do_not_list'
+
   customizations:
     javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
     css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
index 0c52bfa7a0d074c2652a95b7731722af51ce68ff..b5244756df34bc1e426e149cf972f1a6b916e512 100644 (file)
@@ -158,7 +158,16 @@ function getAbout (req: express.Request, res: express.Response) {
       name: CONFIG.INSTANCE.NAME,
       shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
       description: CONFIG.INSTANCE.DESCRIPTION,
-      terms: CONFIG.INSTANCE.TERMS
+      terms: CONFIG.INSTANCE.TERMS,
+      codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
+
+      moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
+      administrator: CONFIG.INSTANCE.ADMINISTRATOR,
+      maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
+      businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
+
+      languages: CONFIG.INSTANCE.LANGUAGES,
+      categories: CONFIG.INSTANCE.CATEGORIES
     }
   }
 
@@ -221,6 +230,16 @@ function customConfig (): CustomConfig {
       shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
       description: CONFIG.INSTANCE.DESCRIPTION,
       terms: CONFIG.INSTANCE.TERMS,
+      codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
+
+      moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
+      administrator: CONFIG.INSTANCE.ADMINISTRATOR,
+      maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
+      businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
+
+      languages: CONFIG.INSTANCE.LANGUAGES,
+      categories: CONFIG.INSTANCE.CATEGORIES,
+
       isNSFW: CONFIG.INSTANCE.IS_NSFW,
       defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
       defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
index 599f3f5acd79173e22e65d00e6cec3105ca1bf5b..4e2b07e64462add98c2a9fbe455704feefbd072e 100644 (file)
@@ -209,6 +209,16 @@ const CONFIG = {
     get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
     get DESCRIPTION () { return config.get<string>('instance.description') },
     get TERMS () { return config.get<string>('instance.terms') },
+    get CODE_OF_CONDUCT () { return config.get<string>('instance.code_of_conduct') },
+
+    get MODERATION_INFORMATION () { return config.get<string>('instance.moderation_information') },
+    get ADMINISTRATOR () { return config.get<string>('instance.administrator') },
+    get MAINTENANCE_LIFETIME () { return config.get<string>('instance.maintenance_lifetime') },
+    get BUSINESS_MODEL () { return config.get<string>('instance.business_model') },
+
+    get LANGUAGES () { return config.get<string[]>('instance.languages') || [] },
+    get CATEGORIES () { return config.get<number[]>('instance.categories') || [] },
+
     get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') },
     get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
     get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
index 1221735c5991224df1d400966d63ee46ba1c33e6..f716dc673ce277063a0a6eec73fe995e73d091b9 100644 (file)
@@ -27,6 +27,16 @@ describe('Test config API validators', function () {
       shortDescription: 'my short description',
       description: 'my super description',
       terms: 'my super terms',
+      codeOfConduct: 'my super coc',
+
+      moderationInformation: 'my super moderation information',
+      administrator: 'Kuja',
+      maintenanceLifetime: 'forever',
+      businessModel: 'my super business model',
+
+      languages: [ 'en', 'es' ],
+      categories: [ 1, 2 ],
+
       isNSFW: true,
       defaultClientRoute: '/videos/recently-added',
       defaultNSFWPolicy: 'blur',
index b2f1933d10baa5429c8fec51f10147d36b75e50b..da75495a566fc2a4c98abe204c402ae876febe85 100644 (file)
@@ -28,7 +28,17 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
     'with WebTorrent and Angular.'
   )
   expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
+
   expect(data.instance.terms).to.equal('No terms for now.')
+  expect(data.instance.codeOfConduct).to.be.empty
+  expect(data.instance.moderationInformation).to.be.empty
+  expect(data.instance.administrator).to.be.empty
+  expect(data.instance.maintenanceLifetime).to.be.empty
+  expect(data.instance.businessModel).to.be.empty
+
+  expect(data.instance.languages).to.have.lengthOf(0)
+  expect(data.instance.categories).to.have.lengthOf(0)
+
   expect(data.instance.defaultClientRoute).to.equal('/videos/trending')
   expect(data.instance.isNSFW).to.be.false
   expect(data.instance.defaultNSFWPolicy).to.equal('display')
@@ -78,7 +88,17 @@ function checkUpdatedConfig (data: CustomConfig) {
   expect(data.instance.name).to.equal('PeerTube updated')
   expect(data.instance.shortDescription).to.equal('my short description')
   expect(data.instance.description).to.equal('my super description')
+
   expect(data.instance.terms).to.equal('my super terms')
+  expect(data.instance.codeOfConduct).to.equal('my super coc')
+  expect(data.instance.moderationInformation).to.equal('my super moderation information')
+  expect(data.instance.administrator).to.equal('Kuja')
+  expect(data.instance.maintenanceLifetime).to.equal('forever')
+  expect(data.instance.businessModel).to.equal('my super business model')
+
+  expect(data.instance.languages).to.deep.equal([ 'en', 'es' ])
+  expect(data.instance.categories).to.deep.equal([ 1, 2 ])
+
   expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added')
   expect(data.instance.isNSFW).to.be.true
   expect(data.instance.defaultNSFWPolicy).to.equal('blur')
@@ -190,6 +210,16 @@ describe('Test config', function () {
         shortDescription: 'my short description',
         description: 'my super description',
         terms: 'my super terms',
+        codeOfConduct: 'my super coc',
+
+        moderationInformation: 'my super moderation information',
+        administrator: 'Kuja',
+        maintenanceLifetime: 'forever',
+        businessModel: 'my super business model',
+
+        languages: [ 'en', 'es' ],
+        categories: [ 1, 2 ],
+
         defaultClientRoute: '/videos/recently-added',
         isNSFW: true,
         defaultNSFWPolicy: 'blur' as 'blur',
index d784af9a93f3f14954890765abd8c4ba1d1dcc0c..785421c98d48ce068ae6054b34bd14d8095511b8 100644 (file)
@@ -53,6 +53,16 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti
       shortDescription: 'my short description',
       description: 'my super description',
       terms: 'my super terms',
+      codeOfConduct: 'my super coc',
+
+      moderationInformation: 'my super moderation information',
+      administrator: 'Kuja',
+      maintenanceLifetime: 'forever',
+      businessModel: 'my super business model',
+
+      languages: [ 'en', 'es' ],
+      categories: [ 1, 2 ],
+
       defaultClientRoute: '/videos/recently-added',
       isNSFW: true,
       defaultNSFWPolicy: 'blur',
index 10dff8b8f0e0e2e98efa9fb4abd0251f5e3fbe1f..e32ed26ee267e75258f411618d613c1961238501 100644 (file)
@@ -4,5 +4,15 @@ export interface About {
     shortDescription: string
     description: string
     terms: string
+
+    codeOfConduct: string
+
+    moderationInformation: string
+    administrator: string
+    maintenanceLifetime: string
+    businessModel: string
+
+    languages: string[]
+    categories: number[]
   }
 }
index 1073ba32c60f45b5ae2c83e58ee3b32221ab8f13..0c331a820601068f06c4a5b6128cf5cac900b018 100644 (file)
@@ -6,6 +6,16 @@ export interface CustomConfig {
     shortDescription: string
     description: string
     terms: string
+    codeOfConduct: string
+
+    moderationInformation: string
+    administrator: string
+    maintenanceLifetime: string
+    businessModel: string
+
+    languages: string[]
+    categories: number[]
+
     isNSFW: boolean
     defaultClientRoute: string
     defaultNSFWPolicy: NSFWPolicyType