diff options
22 files changed, 201 insertions, 73 deletions
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 c568a43b4..0fe2aa203 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,6 +2,41 @@ | |||
2 | 2 | ||
3 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | 3 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> |
4 | 4 | ||
5 | <div class="inner-form-title">Instance</div> | ||
6 | |||
7 | <div class="form-group"> | ||
8 | <label for="instanceName">Name</label> | ||
9 | <input | ||
10 | type="text" id="instanceName" | ||
11 | formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }" | ||
12 | > | ||
13 | <div *ngIf="formErrors.instanceName" class="form-error"> | ||
14 | {{ formErrors.instanceName }} | ||
15 | </div> | ||
16 | </div> | ||
17 | |||
18 | <div class="form-group"> | ||
19 | <label for="instanceDescription">Description (markdown)</label> | ||
20 | <my-markdown-textarea | ||
21 | id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true" | ||
22 | [classes]="{ 'input-error': formErrors['instanceDescription'] }" | ||
23 | ></my-markdown-textarea> | ||
24 | <div *ngIf="formErrors.instanceDescription" class="form-error"> | ||
25 | {{ formErrors.instanceDescription }} | ||
26 | </div> | ||
27 | </div> | ||
28 | |||
29 | <div class="form-group"> | ||
30 | <label for="instanceTerms">Terms (markdown)</label> | ||
31 | <my-markdown-textarea | ||
32 | id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true" | ||
33 | [ngClass]="{ 'input-error': formErrors['instanceTerms'] }" | ||
34 | ></my-markdown-textarea> | ||
35 | <div *ngIf="formErrors.instanceTerms" class="form-error"> | ||
36 | {{ formErrors.instanceTerms }} | ||
37 | </div> | ||
38 | </div> | ||
39 | |||
5 | <div class="inner-form-title">Cache</div> | 40 | <div class="inner-form-title">Cache</div> |
6 | 41 | ||
7 | <div class="form-group"> | 42 | <div class="form-group"> |
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 1b3522786..cd8c926f7 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 | |||
@@ -4,7 +4,13 @@ import { Router } from '@angular/router' | |||
4 | import { ConfigService } from '@app/+admin/config/shared/config.service' | 4 | import { ConfigService } from '@app/+admin/config/shared/config.service' |
5 | import { ServerService } from '@app/core/server/server.service' | 5 | import { ServerService } from '@app/core/server/server.service' |
6 | import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared' | 6 | import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared' |
7 | import { ADMIN_EMAIL, CACHE_PREVIEWS_SIZE, SIGNUP_LIMIT, TRANSCODING_THREADS } from '@app/shared/forms/form-validators/custom-config' | 7 | import { |
8 | ADMIN_EMAIL, | ||
9 | CACHE_PREVIEWS_SIZE, | ||
10 | INSTANCE_NAME, | ||
11 | SIGNUP_LIMIT, | ||
12 | TRANSCODING_THREADS | ||
13 | } from '@app/shared/forms/form-validators/custom-config' | ||
8 | import { NotificationsService } from 'angular2-notifications' | 14 | import { NotificationsService } from 'angular2-notifications' |
9 | import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model' | 15 | import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model' |
10 | 16 | ||
@@ -36,6 +42,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
36 | 42 | ||
37 | form: FormGroup | 43 | form: FormGroup |
38 | formErrors = { | 44 | formErrors = { |
45 | instanceName: '', | ||
46 | instanceDescription: '', | ||
47 | instanceTerms: '', | ||
39 | cachePreviewsSize: '', | 48 | cachePreviewsSize: '', |
40 | signupLimit: '', | 49 | signupLimit: '', |
41 | adminEmail: '', | 50 | adminEmail: '', |
@@ -43,6 +52,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
43 | transcodingThreads: '' | 52 | transcodingThreads: '' |
44 | } | 53 | } |
45 | validationMessages = { | 54 | validationMessages = { |
55 | instanceName: INSTANCE_NAME.MESSAGES, | ||
46 | cachePreviewsSize: CACHE_PREVIEWS_SIZE.MESSAGES, | 56 | cachePreviewsSize: CACHE_PREVIEWS_SIZE.MESSAGES, |
47 | signupLimit: SIGNUP_LIMIT.MESSAGES, | 57 | signupLimit: SIGNUP_LIMIT.MESSAGES, |
48 | adminEmail: ADMIN_EMAIL.MESSAGES, | 58 | adminEmail: ADMIN_EMAIL.MESSAGES, |
@@ -65,6 +75,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
65 | 75 | ||
66 | buildForm () { | 76 | buildForm () { |
67 | const formGroupData = { | 77 | const formGroupData = { |
78 | instanceName: [ '', INSTANCE_NAME.VALIDATORS ], | ||
79 | instanceDescription: [ '' ], | ||
80 | instanceTerms: [ '' ], | ||
68 | cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ], | 81 | cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ], |
69 | signupEnabled: [ ], | 82 | signupEnabled: [ ], |
70 | signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ], | 83 | signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ], |
@@ -109,6 +122,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
109 | 122 | ||
110 | formValidated () { | 123 | formValidated () { |
111 | const data = { | 124 | const data = { |
125 | instance: { | ||
126 | name: this.form.value['instanceName'], | ||
127 | description: this.form.value['instanceDescription'], | ||
128 | terms: this.form.value['instanceTerms'] | ||
129 | }, | ||
112 | cache: { | 130 | cache: { |
113 | previews: { | 131 | previews: { |
114 | size: this.form.value['cachePreviewsSize'] | 132 | size: this.form.value['cachePreviewsSize'] |
@@ -146,6 +164,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
146 | this.serverService.loadConfig() | 164 | this.serverService.loadConfig() |
147 | 165 | ||
148 | this.updateForm() | 166 | this.updateForm() |
167 | |||
168 | this.notificationsService.success('Success', 'Configuration updated.') | ||
149 | }, | 169 | }, |
150 | 170 | ||
151 | err => this.notificationsService.error('Error', err.message) | 171 | err => this.notificationsService.error('Error', err.message) |
@@ -154,6 +174,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
154 | 174 | ||
155 | private updateForm () { | 175 | private updateForm () { |
156 | const data = { | 176 | const data = { |
177 | instanceName: this.customConfig.instance.name, | ||
178 | instanceDescription: this.customConfig.instance.description, | ||
179 | instanceTerms: this.customConfig.instance.terms, | ||
157 | cachePreviewsSize: this.customConfig.cache.previews.size, | 180 | cachePreviewsSize: this.customConfig.cache.previews.size, |
158 | signupEnabled: this.customConfig.signup.enabled, | 181 | signupEnabled: this.customConfig.signup.enabled, |
159 | signupLimit: this.customConfig.signup.limit, | 182 | signupLimit: this.customConfig.signup.limit, |
diff --git a/client/src/app/shared/forms/form-validators/custom-config.ts b/client/src/app/shared/forms/form-validators/custom-config.ts index 17ae0e75c..9e3fa98d8 100644 --- a/client/src/app/shared/forms/form-validators/custom-config.ts +++ b/client/src/app/shared/forms/form-validators/custom-config.ts | |||
@@ -1,5 +1,12 @@ | |||
1 | import { Validators } from '@angular/forms' | 1 | import { Validators } from '@angular/forms' |
2 | 2 | ||
3 | export const INSTANCE_NAME = { | ||
4 | VALIDATORS: [ Validators.required ], | ||
5 | MESSAGES: { | ||
6 | 'required': 'Instance name is required.', | ||
7 | } | ||
8 | } | ||
9 | |||
3 | export const CACHE_PREVIEWS_SIZE = { | 10 | export const CACHE_PREVIEWS_SIZE = { |
4 | VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], | 11 | VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], |
5 | MESSAGES: { | 12 | MESSAGES: { |
diff --git a/client/src/app/shared/forms/markdown-textarea.component.html b/client/src/app/shared/forms/markdown-textarea.component.html new file mode 100644 index 000000000..d2d4cf95c --- /dev/null +++ b/client/src/app/shared/forms/markdown-textarea.component.html | |||
@@ -0,0 +1,12 @@ | |||
1 | <div class="root" [ngStyle]="{ 'flex-direction': flexDirection }"> | ||
2 | <textarea | ||
3 | [(ngModel)]="description" (ngModelChange)="onModelChange()" | ||
4 | [ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }" | ||
5 | id="description" name="description"> | ||
6 | </textarea> | ||
7 | |||
8 | <tabset *ngIf="arePreviewsDisplayed()" #staticTabs class="previews"> | ||
9 | <tab *ngIf="truncate !== undefined" heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab> | ||
10 | <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab> | ||
11 | </tabset> | ||
12 | </div> | ||
diff --git a/client/src/app/shared/forms/markdown-textarea.component.scss b/client/src/app/shared/forms/markdown-textarea.component.scss new file mode 100644 index 000000000..82aff541d --- /dev/null +++ b/client/src/app/shared/forms/markdown-textarea.component.scss | |||
@@ -0,0 +1,27 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .root { | ||
5 | display: flex; | ||
6 | |||
7 | textarea { | ||
8 | @include peertube-textarea(100%, 150px); | ||
9 | |||
10 | margin-bottom: 15px; | ||
11 | } | ||
12 | |||
13 | /deep/ { | ||
14 | .nav-link { | ||
15 | display: flex !important; | ||
16 | align-items: center; | ||
17 | height: 30px !important; | ||
18 | padding: 0 15px !important; | ||
19 | } | ||
20 | |||
21 | .tab-content { | ||
22 | min-height: 75px; | ||
23 | padding: 15px; | ||
24 | font-size: 15px; | ||
25 | } | ||
26 | } | ||
27 | } | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.ts b/client/src/app/shared/forms/markdown-textarea.component.ts index eba345412..20f13b28c 100644 --- a/client/src/app/videos/+video-edit/shared/video-description.component.ts +++ b/client/src/app/shared/forms/markdown-textarea.component.ts | |||
@@ -3,25 +3,33 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | |||
3 | import 'rxjs/add/operator/debounceTime' | 3 | import 'rxjs/add/operator/debounceTime' |
4 | import 'rxjs/add/operator/distinctUntilChanged' | 4 | import 'rxjs/add/operator/distinctUntilChanged' |
5 | import { isInMobileView } from '@app/shared/misc/utils' | 5 | import { isInMobileView } from '@app/shared/misc/utils' |
6 | import { MarkdownService } from '@app/videos/shared' | ||
6 | import { Subject } from 'rxjs/Subject' | 7 | import { Subject } from 'rxjs/Subject' |
7 | import { MarkdownService } from '../../shared' | ||
8 | import truncate from 'lodash-es/truncate' | 8 | import truncate from 'lodash-es/truncate' |
9 | 9 | ||
10 | @Component({ | 10 | @Component({ |
11 | selector: 'my-video-description', | 11 | selector: 'my-markdown-textarea', |
12 | templateUrl: './video-description.component.html', | 12 | templateUrl: './markdown-textarea.component.html', |
13 | styleUrls: [ './video-description.component.scss' ], | 13 | styleUrls: [ './markdown-textarea.component.scss' ], |
14 | providers: [ | 14 | providers: [ |
15 | { | 15 | { |
16 | provide: NG_VALUE_ACCESSOR, | 16 | provide: NG_VALUE_ACCESSOR, |
17 | useExisting: forwardRef(() => VideoDescriptionComponent), | 17 | useExisting: forwardRef(() => MarkdownTextareaComponent), |
18 | multi: true | 18 | multi: true |
19 | } | 19 | } |
20 | ] | 20 | ] |
21 | }) | 21 | }) |
22 | 22 | ||
23 | export class VideoDescriptionComponent implements ControlValueAccessor, OnInit { | 23 | export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { |
24 | @Input() description = '' | 24 | @Input() description = '' |
25 | @Input() classes: string[] = [] | ||
26 | @Input() textareaWidth = '100%' | ||
27 | @Input() textareaHeight = '150px' | ||
28 | @Input() previewColumn = false | ||
29 | @Input() truncate: number | ||
30 | |||
31 | textareaMarginRight = '0' | ||
32 | flexDirection = 'column' | ||
25 | truncatedDescriptionHTML = '' | 33 | truncatedDescriptionHTML = '' |
26 | descriptionHTML = '' | 34 | descriptionHTML = '' |
27 | 35 | ||
@@ -36,6 +44,11 @@ export class VideoDescriptionComponent implements ControlValueAccessor, OnInit { | |||
36 | .subscribe(() => this.updateDescriptionPreviews()) | 44 | .subscribe(() => this.updateDescriptionPreviews()) |
37 | 45 | ||
38 | this.descriptionChanged.next(this.description) | 46 | this.descriptionChanged.next(this.description) |
47 | |||
48 | if (this.previewColumn) { | ||
49 | this.flexDirection = 'row' | ||
50 | this.textareaMarginRight = '15px' | ||
51 | } | ||
39 | } | 52 | } |
40 | 53 | ||
41 | propagateChange = (_: any) => { /* empty */ } | 54 | propagateChange = (_: any) => { /* empty */ } |
@@ -65,9 +78,9 @@ export class VideoDescriptionComponent implements ControlValueAccessor, OnInit { | |||
65 | } | 78 | } |
66 | 79 | ||
67 | private updateDescriptionPreviews () { | 80 | private updateDescriptionPreviews () { |
68 | if (!this.description) return | 81 | if (this.description === null || this.description === undefined) return |
69 | 82 | ||
70 | this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 })) | 83 | this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: this.truncate })) |
71 | this.descriptionHTML = this.markdownService.markdownToHTML(this.description) | 84 | this.descriptionHTML = this.markdownService.markdownToHTML(this.description) |
72 | } | 85 | } |
73 | } | 86 | } |
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index a5c56cb46..d8f98bdf6 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -3,10 +3,13 @@ import { HttpClientModule } from '@angular/common/http' | |||
3 | import { NgModule } from '@angular/core' | 3 | import { NgModule } from '@angular/core' |
4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' |
5 | import { RouterModule } from '@angular/router' | 5 | import { RouterModule } from '@angular/router' |
6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | ||
7 | import { MarkdownService } from '@app/videos/shared' | ||
6 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' | 8 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' |
7 | 9 | ||
8 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown' | 10 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown' |
9 | import { ModalModule } from 'ngx-bootstrap/modal' | 11 | import { ModalModule } from 'ngx-bootstrap/modal' |
12 | import { TabsModule } from 'ngx-bootstrap/tabs' | ||
10 | import { InfiniteScrollModule } from 'ngx-infinite-scroll' | 13 | import { InfiniteScrollModule } from 'ngx-infinite-scroll' |
11 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | 14 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' |
12 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' | 15 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' |
@@ -40,7 +43,8 @@ import { VideoService } from './video/video.service' | |||
40 | 43 | ||
41 | PrimeSharedModule, | 44 | PrimeSharedModule, |
42 | InfiniteScrollModule, | 45 | InfiniteScrollModule, |
43 | NgPipesModule | 46 | NgPipesModule, |
47 | TabsModule.forRoot() | ||
44 | ], | 48 | ], |
45 | 49 | ||
46 | declarations: [ | 50 | declarations: [ |
@@ -50,7 +54,8 @@ import { VideoService } from './video/video.service' | |||
50 | DeleteButtonComponent, | 54 | DeleteButtonComponent, |
51 | EditButtonComponent, | 55 | EditButtonComponent, |
52 | NumberFormatterPipe, | 56 | NumberFormatterPipe, |
53 | FromNowPipe | 57 | FromNowPipe, |
58 | MarkdownTextareaComponent | ||
54 | ], | 59 | ], |
55 | 60 | ||
56 | exports: [ | 61 | exports: [ |
@@ -74,6 +79,7 @@ import { VideoService } from './video/video.service' | |||
74 | VideoMiniatureComponent, | 79 | VideoMiniatureComponent, |
75 | DeleteButtonComponent, | 80 | DeleteButtonComponent, |
76 | EditButtonComponent, | 81 | EditButtonComponent, |
82 | MarkdownTextareaComponent, | ||
77 | 83 | ||
78 | NumberFormatterPipe, | 84 | NumberFormatterPipe, |
79 | FromNowPipe | 85 | FromNowPipe |
@@ -86,7 +92,8 @@ import { VideoService } from './video/video.service' | |||
86 | VideoAbuseService, | 92 | VideoAbuseService, |
87 | VideoBlacklistService, | 93 | VideoBlacklistService, |
88 | UserService, | 94 | UserService, |
89 | VideoService | 95 | VideoService, |
96 | MarkdownService | ||
90 | ] | 97 | ] |
91 | }) | 98 | }) |
92 | export class SharedModule { } | 99 | export class SharedModule { } |
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index 50761ca0c..d4f5e258f 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts | |||
@@ -42,10 +42,10 @@ export class VideoService { | |||
42 | } | 42 | } |
43 | 43 | ||
44 | updateVideo (video: VideoEdit) { | 44 | updateVideo (video: VideoEdit) { |
45 | const language = video.language || undefined | 45 | const language = video.language || null |
46 | const licence = video.licence || undefined | 46 | const licence = video.licence || null |
47 | const category = video.category || undefined | 47 | const category = video.category || null |
48 | const description = video.description || undefined | 48 | const description = video.description || null |
49 | 49 | ||
50 | const body: VideoUpdate = { | 50 | const body: VideoUpdate = { |
51 | name: video.name, | 51 | name: video.name, |
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.html b/client/src/app/videos/+video-edit/shared/video-description.component.html deleted file mode 100644 index 989292c47..000000000 --- a/client/src/app/videos/+video-edit/shared/video-description.component.html +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | <textarea | ||
2 | [(ngModel)]="description" (ngModelChange)="onModelChange()" | ||
3 | id="description" name="description"> | ||
4 | </textarea> | ||
5 | |||
6 | <tabset *ngIf="arePreviewsDisplayed()" #staticTabs class="previews"> | ||
7 | <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab> | ||
8 | <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab> | ||
9 | </tabset> | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.scss b/client/src/app/videos/+video-edit/shared/video-description.component.scss deleted file mode 100644 index 2c731bee3..000000000 --- a/client/src/app/videos/+video-edit/shared/video-description.component.scss +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | textarea { | ||
5 | @include peertube-textarea(100%, 150px); | ||
6 | |||
7 | margin-bottom: 15px; | ||
8 | } | ||
9 | |||
10 | /deep/ { | ||
11 | .nav-link { | ||
12 | display: flex !important; | ||
13 | align-items: center; | ||
14 | height: 30px !important; | ||
15 | padding: 0 15px !important; | ||
16 | } | ||
17 | |||
18 | .tab-content { | ||
19 | min-height: 75px; | ||
20 | padding: 15px; | ||
21 | font-size: 15px; | ||
22 | } | ||
23 | } | ||
24 | |||
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 80377933e..d031825bd 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 | |||
@@ -12,14 +12,14 @@ | |||
12 | <div class="form-group"> | 12 | <div class="form-group"> |
13 | <label class="label-tags">Tags</label> <span>(press Enter to add)</span> | 13 | <label class="label-tags">Tags</label> <span>(press Enter to add)</span> |
14 | <tag-input | 14 | <tag-input |
15 | [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | 15 | [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" |
16 | formControlName="tags" maxItems="5" modelAsStrings="true" | 16 | formControlName="tags" maxItems="5" modelAsStrings="true" |
17 | ></tag-input> | 17 | ></tag-input> |
18 | </div> | 18 | </div> |
19 | 19 | ||
20 | <div class="form-group"> | 20 | <div class="form-group"> |
21 | <label for="description">Description</label> | 21 | <label for="description">Description</label> |
22 | <my-video-description formControlName="description"></my-video-description> | 22 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> |
23 | 23 | ||
24 | <div *ngIf="formErrors.description" class="form-error"> | 24 | <div *ngIf="formErrors.description" class="form-error"> |
25 | {{ formErrors.description }} | 25 | {{ formErrors.description }} |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts index ce106d82f..098a71ae6 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts | |||
@@ -1,23 +1,17 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | |||
3 | import { TagInputModule } from 'ngx-chips' | ||
4 | import { TabsModule } from 'ngx-bootstrap/tabs' | 2 | import { TabsModule } from 'ngx-bootstrap/tabs' |
5 | 3 | import { TagInputModule } from 'ngx-chips' | |
6 | import { MarkdownService } from '../../shared' | ||
7 | import { SharedModule } from '../../../shared' | 4 | import { SharedModule } from '../../../shared' |
8 | import { VideoDescriptionComponent } from './video-description.component' | ||
9 | import { VideoEditComponent } from './video-edit.component' | 5 | import { VideoEditComponent } from './video-edit.component' |
10 | 6 | ||
11 | @NgModule({ | 7 | @NgModule({ |
12 | imports: [ | 8 | imports: [ |
13 | TagInputModule, | 9 | TagInputModule, |
14 | TabsModule.forRoot(), | ||
15 | 10 | ||
16 | SharedModule | 11 | SharedModule |
17 | ], | 12 | ], |
18 | 13 | ||
19 | declarations: [ | 14 | declarations: [ |
20 | VideoDescriptionComponent, | ||
21 | VideoEditComponent | 15 | VideoEditComponent |
22 | ], | 16 | ], |
23 | 17 | ||
@@ -25,12 +19,9 @@ import { VideoEditComponent } from './video-edit.component' | |||
25 | TagInputModule, | 19 | TagInputModule, |
26 | TabsModule, | 20 | TabsModule, |
27 | 21 | ||
28 | VideoDescriptionComponent, | ||
29 | VideoEditComponent | 22 | VideoEditComponent |
30 | ], | 23 | ], |
31 | 24 | ||
32 | providers: [ | 25 | providers: [] |
33 | MarkdownService | ||
34 | ] | ||
35 | }) | 26 | }) |
36 | export class VideoEditModule { } | 27 | export class VideoEditModule { } |
diff --git a/client/src/app/videos/shared/markdown.service.ts b/client/src/app/videos/shared/markdown.service.ts index 82745f0c6..fd0330f9b 100644 --- a/client/src/app/videos/shared/markdown.service.ts +++ b/client/src/app/videos/shared/markdown.service.ts | |||
@@ -14,6 +14,17 @@ export class MarkdownService { | |||
14 | .enable('link') | 14 | .enable('link') |
15 | .enable('newline') | 15 | .enable('newline') |
16 | 16 | ||
17 | this.setTargetToLinks() | ||
18 | } | ||
19 | |||
20 | markdownToHTML (markdown: string) { | ||
21 | const html = this.markdownIt.render(markdown) | ||
22 | |||
23 | // Avoid linkify truncated links | ||
24 | return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...') | ||
25 | } | ||
26 | |||
27 | private setTargetToLinks () { | ||
17 | // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer | 28 | // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer |
18 | const defaultRender = this.markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) { | 29 | const defaultRender = this.markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) { |
19 | return self.renderToken(tokens, idx, options) | 30 | return self.renderToken(tokens, idx, options) |
@@ -33,11 +44,4 @@ export class MarkdownService { | |||
33 | return defaultRender(tokens, idx, options, env, self) | 44 | return defaultRender(tokens, idx, options, env, self) |
34 | } | 45 | } |
35 | } | 46 | } |
36 | |||
37 | markdownToHTML (markdown: string) { | ||
38 | const html = this.markdownIt.render(markdown) | ||
39 | |||
40 | // Avoid linkify truncated links | ||
41 | return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...') | ||
42 | } | ||
43 | } | 47 | } |
diff --git a/config/default.yaml b/config/default.yaml index 691c9e00b..6c73c5fea 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -69,3 +69,8 @@ transcoding: | |||
69 | 480p: true | 69 | 480p: true |
70 | 720p: true | 70 | 720p: true |
71 | 1080p: true | 71 | 1080p: true |
72 | |||
73 | instance: | ||
74 | name: 'PeerTube' | ||
75 | description: '' # Support markdown | ||
76 | terms: '' # Support markdown | ||
diff --git a/config/production.yaml.example b/config/production.yaml.example index 04354b75d..e476bbcfa 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -69,3 +69,8 @@ transcoding: | |||
69 | 480p: true | 69 | 480p: true |
70 | 720p: true | 70 | 720p: true |
71 | 1080p: true | 71 | 1080p: true |
72 | |||
73 | instance: | ||
74 | name: 'PeerTube' | ||
75 | description: '' # Support markdown | ||
76 | terms: '' # Support markdown | ||
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index bc804d9ba..e4cb02820 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -105,6 +105,11 @@ export { | |||
105 | 105 | ||
106 | function customConfig (): CustomConfig { | 106 | function customConfig (): CustomConfig { |
107 | return { | 107 | return { |
108 | instance: { | ||
109 | name: CONFIG.INSTANCE.NAME, | ||
110 | description: CONFIG.INSTANCE.DESCRIPTION, | ||
111 | terms: CONFIG.INSTANCE.TERMS | ||
112 | }, | ||
108 | cache: { | 113 | cache: { |
109 | previews: { | 114 | previews: { |
110 | size: CONFIG.CACHE.PREVIEWS.SIZE | 115 | size: CONFIG.CACHE.PREVIEWS.SIZE |
diff --git a/server/initializers/checker.ts b/server/initializers/checker.ts index d550fd23f..e5cc1b7be 100644 --- a/server/initializers/checker.ts +++ b/server/initializers/checker.ts | |||
@@ -23,7 +23,8 @@ function checkMissedConfig () { | |||
23 | 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', | 23 | 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', |
24 | 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews', 'storage.torrents', 'storage.cache', 'log.level', | 24 | 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews', 'storage.torrents', 'storage.cache', 'log.level', |
25 | 'cache.previews.size', 'admin.email', 'signup.enabled', 'signup.limit', 'transcoding.enabled', 'transcoding.threads', | 25 | 'cache.previews.size', 'admin.email', 'signup.enabled', 'signup.limit', 'transcoding.enabled', 'transcoding.threads', |
26 | 'user.video_quota', 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address' | 26 | 'user.video_quota', 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', |
27 | 'instance.name', 'instance.description', 'instance.terms' | ||
27 | ] | 28 | ] |
28 | const miss: string[] = [] | 29 | const miss: string[] = [] |
29 | 30 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index e7b1656e2..e531c4c39 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -154,6 +154,11 @@ const CONFIG = { | |||
154 | PREVIEWS: { | 154 | PREVIEWS: { |
155 | get SIZE () { return config.get<number>('cache.previews.size') } | 155 | get SIZE () { return config.get<number>('cache.previews.size') } |
156 | } | 156 | } |
157 | }, | ||
158 | INSTANCE: { | ||
159 | get NAME () { return config.get<string>('instance.name') }, | ||
160 | get DESCRIPTION () { return config.get<string>('instance.description') }, | ||
161 | get TERMS () { return config.get<string>('instance.terms') } | ||
157 | } | 162 | } |
158 | } | 163 | } |
159 | 164 | ||
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index a2a404702..efc1e4e09 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -14,6 +14,11 @@ describe('Test config API validators', function () { | |||
14 | let server: ServerInfo | 14 | let server: ServerInfo |
15 | let userAccessToken: string | 15 | let userAccessToken: string |
16 | const updateParams: CustomConfig = { | 16 | const updateParams: CustomConfig = { |
17 | instance: { | ||
18 | name: 'PeerTube updated', | ||
19 | description: 'my super description', | ||
20 | terms: 'my super terms' | ||
21 | }, | ||
17 | cache: { | 22 | cache: { |
18 | previews: { | 23 | previews: { |
19 | size: 2 | 24 | size: 2 |
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index a1f8212bb..f83e21e82 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -49,6 +49,9 @@ describe('Test config', function () { | |||
49 | const res = await getCustomConfig(server.url, server.accessToken) | 49 | const res = await getCustomConfig(server.url, server.accessToken) |
50 | const data = res.body | 50 | const data = res.body |
51 | 51 | ||
52 | expect(data.instance.name).to.equal('PeerTube') | ||
53 | expect(data.instance.description).to.be.empty | ||
54 | expect(data.instance.terms).to.be.empty | ||
52 | expect(data.cache.previews.size).to.equal(1) | 55 | expect(data.cache.previews.size).to.equal(1) |
53 | expect(data.signup.enabled).to.be.true | 56 | expect(data.signup.enabled).to.be.true |
54 | expect(data.signup.limit).to.equal(4) | 57 | expect(data.signup.limit).to.equal(4) |
@@ -65,6 +68,11 @@ describe('Test config', function () { | |||
65 | 68 | ||
66 | it('Should update the customized configuration', async function () { | 69 | it('Should update the customized configuration', async function () { |
67 | const newCustomConfig = { | 70 | const newCustomConfig = { |
71 | instance: { | ||
72 | name: 'PeerTube updated', | ||
73 | description: 'my super description', | ||
74 | terms: 'my super terms' | ||
75 | }, | ||
68 | cache: { | 76 | cache: { |
69 | previews: { | 77 | previews: { |
70 | size: 2 | 78 | size: 2 |
@@ -97,7 +105,9 @@ describe('Test config', function () { | |||
97 | const res = await getCustomConfig(server.url, server.accessToken) | 105 | const res = await getCustomConfig(server.url, server.accessToken) |
98 | const data = res.body | 106 | const data = res.body |
99 | 107 | ||
100 | expect(data.cache.previews.size).to.equal(2) | 108 | expect(data.instance.name).to.equal('PeerTube updated') |
109 | expect(data.instance.description).to.equal('my super description') | ||
110 | expect(data.instance.terms).to.equal('my super terms') | ||
101 | expect(data.signup.enabled).to.be.false | 111 | expect(data.signup.enabled).to.be.false |
102 | expect(data.signup.limit).to.equal(5) | 112 | expect(data.signup.limit).to.equal(5) |
103 | expect(data.admin.email).to.equal('superadmin1@example.com') | 113 | expect(data.admin.email).to.equal('superadmin1@example.com') |
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index 860f04fd8..0b28edd48 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts | |||
@@ -373,7 +373,7 @@ async function completeVideoCheck ( | |||
373 | expect(dateIsValid(video.createdAt)).to.be.true | 373 | expect(dateIsValid(video.createdAt)).to.be.true |
374 | expect(dateIsValid(video.updatedAt)).to.be.true | 374 | expect(dateIsValid(video.updatedAt)).to.be.true |
375 | 375 | ||
376 | const res = await getVideo(url, video.id) | 376 | const res = await getVideo(url, video.uuid) |
377 | const videoDetails = res.body | 377 | const videoDetails = res.body |
378 | 378 | ||
379 | expect(videoDetails.files).to.have.lengthOf(attributes.files.length) | 379 | expect(videoDetails.files).to.have.lengthOf(attributes.files.length) |
diff --git a/shared/models/config/custom-config.model.ts b/shared/models/config/custom-config.model.ts index 73b5b6a72..6ef0fc5a2 100644 --- a/shared/models/config/custom-config.model.ts +++ b/shared/models/config/custom-config.model.ts | |||
@@ -1,4 +1,10 @@ | |||
1 | export interface CustomConfig { | 1 | export interface CustomConfig { |
2 | instance: { | ||
3 | name: string | ||
4 | description: string | ||
5 | terms: string | ||
6 | } | ||
7 | |||
2 | cache: { | 8 | cache: { |
3 | previews: { | 9 | previews: { |
4 | size: number | 10 | size: number |