aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-07-21 15:51:30 +0200
committerChocobozzz <me@florianbigard.com>2021-07-21 15:51:30 +0200
commita24bd1ed41b43790bab6ba789580bb4e85f07d85 (patch)
treea54b0f6c921ba83a6e909cd0ced325b2d4b8863c /client
parent5f26f13b3c16ac5ae0a3b0a7142d84a9528cf565 (diff)
parentc63830f15403ac4e750829f27d8bbbdc9a59282c (diff)
downloadPeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.tar.gz
PeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.tar.zst
PeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.zip
Merge branch 'next' into develop
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.ts3
-rw-r--r--client/src/app/+accounts/accounts.component.ts3
-rw-r--r--client/src/app/+admin/admin.component.ts4
-rw-r--r--client/src/app/+admin/admin.module.ts3
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html4
-rw-r--r--client/src/app/+admin/follows/following-list/follow-modal.component.html42
-rw-r--r--client/src/app/+admin/follows/following-list/follow-modal.component.scss3
-rw-r--r--client/src/app/+admin/follows/following-list/follow-modal.component.ts69
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html21
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts22
-rw-r--r--client/src/app/+admin/follows/following-list/index.ts1
-rw-r--r--client/src/app/+admin/follows/follows.routes.ts4
-rw-r--r--client/src/app/+login/login.component.html4
-rw-r--r--client/src/app/+login/login.component.ts4
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts7
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts4
-rw-r--r--client/src/app/+page-not-found/page-not-found.component.ts3
-rw-r--r--client/src/app/+video-channels/video-channels.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts3
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts11
-rw-r--r--client/src/app/core/auth/auth.service.ts3
-rw-r--r--client/src/app/core/rest/rest-extractor.service.ts3
-rw-r--r--client/src/app/helpers/utils.ts2
-rw-r--r--client/src/app/shared/form-validators/batch-domains-validators.ts60
-rw-r--r--client/src/app/shared/form-validators/host-validators.ts105
-rw-r--r--client/src/app/shared/form-validators/host.ts8
-rw-r--r--client/src/app/shared/form-validators/index.ts3
-rw-r--r--client/src/app/shared/shared-instance/instance-follow.service.ts13
-rw-r--r--client/src/app/shared/shared-main/auth/auth-interceptor.service.ts8
-rw-r--r--client/src/app/shared/shared-moderation/batch-domains-modal.component.html14
-rw-r--r--client/src/app/shared/shared-moderation/batch-domains-modal.component.ts8
-rw-r--r--client/src/sass/application.scss8
-rw-r--r--client/src/standalone/videos/embed.ts2
33 files changed, 303 insertions, 151 deletions
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
index a528faa20..37e9feacb 100644
--- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
@@ -11,8 +11,7 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
11import { InstanceService } from '@app/shared/shared-instance' 11import { InstanceService } from '@app/shared/shared-instance'
12import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 12import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
13import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 13import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
14import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 14import { HTMLServerConfig, HttpStatusCode } from '@shared/models'
15import { HTMLServerConfig } from '@shared/models'
16 15
17type Prefill = { 16type Prefill = {
18 subject?: string 17 subject?: string
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts
index c69b04a01..5b59f3cd0 100644
--- a/client/src/app/+accounts/accounts.component.ts
+++ b/client/src/app/+accounts/accounts.component.ts
@@ -13,8 +13,7 @@ import {
13 VideoService 13 VideoService
14} from '@app/shared/shared-main' 14} from '@app/shared/shared-main'
15import { AccountReportComponent } from '@app/shared/shared-moderation' 15import { AccountReportComponent } from '@app/shared/shared-moderation'
16import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 16import { HttpStatusCode, User, UserRight } from '@shared/models'
17import { User, UserRight } from '@shared/models'
18import { AccountSearchComponent } from './account-search/account-search.component' 17import { AccountSearchComponent } from './account-search/account-search.component'
19 18
20@Component({ 19@Component({
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index dd92ed2ca..4b6fab6ed 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -26,12 +26,12 @@ export class AdminComponent implements OnInit {
26 label: $localize`Federation`, 26 label: $localize`Federation`,
27 children: [ 27 children: [
28 { 28 {
29 label: $localize`Instances you follow`, 29 label: $localize`Following`,
30 routerLink: '/admin/follows/following-list', 30 routerLink: '/admin/follows/following-list',
31 iconName: 'following' 31 iconName: 'following'
32 }, 32 },
33 { 33 {
34 label: $localize`Instances following you`, 34 label: $localize`Followers`,
35 routerLink: '/admin/follows/followers-list', 35 routerLink: '/admin/follows/followers-list',
36 iconName: 'follower' 36 iconName: 'follower'
37 }, 37 },
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index a7fe20b07..1ea7b9784 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -25,7 +25,7 @@ import {
25 EditVODTranscodingComponent 25 EditVODTranscodingComponent
26} from './config' 26} from './config'
27import { ConfigService } from './config/shared/config.service' 27import { ConfigService } from './config/shared/config.service'
28import { FollowersListComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows' 28import { FollowersListComponent, FollowModalComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows'
29import { FollowingListComponent } from './follows/following-list/following-list.component' 29import { FollowingListComponent } from './follows/following-list/following-list.component'
30import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component' 30import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component'
31import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component' 31import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component'
@@ -68,6 +68,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
68 FollowsComponent, 68 FollowsComponent,
69 FollowersListComponent, 69 FollowersListComponent,
70 FollowingListComponent, 70 FollowingListComponent,
71 FollowModalComponent,
71 RedundancyCheckboxComponent, 72 RedundancyCheckboxComponent,
72 VideoRedundanciesListComponent, 73 VideoRedundanciesListComponent,
73 VideoRedundancyInformationComponent, 74 VideoRedundancyInformationComponent,
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index c2e9a4df6..08459634d 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -1,6 +1,6 @@
1<h1> 1<h1>
2 <my-global-icon iconName="follower" aria-hidden="true"></my-global-icon> 2 <my-global-icon iconName="follower" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Instances following you</ng-container> 3 <ng-container i18n>Followers of your instance</ng-container>
4</h1> 4</h1>
5 5
6<p-table 6<p-table
@@ -21,7 +21,7 @@
21 <ng-template pTemplate="header"> 21 <ng-template pTemplate="header">
22 <tr> 22 <tr>
23 <th style="width: 150px;" i18n>Actions</th> 23 <th style="width: 150px;" i18n>Actions</th>
24 <th i18n>Follower handle</th> 24 <th i18n>Follower</th>
25 <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> 25 <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
26 <th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th> 26 <th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th>
27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.html b/client/src/app/+admin/follows/following-list/follow-modal.component.html
new file mode 100644
index 000000000..d0761b718
--- /dev/null
+++ b/client/src/app/+admin/follows/following-list/follow-modal.component.html
@@ -0,0 +1,42 @@
1<ng-template #modal>
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Follow</h4>
4
5 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
6 </div>
7
8 <div class="modal-body">
9 <form novalidate [formGroup]="form" (ngSubmit)="submit()">
10 <div class="form-group">
11 <label i18n for="hostsOrHandles">1 host (without "http://"), account handle or channel handle per line</label>
12
13 <textarea
14 [placeholder]="placeholder" formControlName="hostsOrHandles" type="text" id="hostsOrHandles" name="hostsOrHandles"
15 class="form-control" [ngClass]="{ 'input-error': formErrors['hostsOrHandles'] }" ngbAutofocus
16 ></textarea>
17
18 <div *ngIf="formErrors.hostsOrHandles" class="form-error">
19 {{ formErrors.hostsOrHandles }}
20
21 <div *ngIf="form.controls['hostsOrHandles'].errors.validHostsOrHandles">
22 {{ form.controls['hostsOrHandles'].errors.validHostsOrHandles.value }}
23 </div>
24 </div>
25 </div>
26
27 <div i18n *ngIf="httpEnabled() === false" class="alert alert-warning">
28 It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers.
29 </div>
30
31 <div class="form-group inputs">
32 <input
33 type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
34 (click)="hide()" (key.enter)="hide()"
35 >
36
37 <input type="submit" i18n-value value="Follow" class="peertube-button orange-button" [disabled]="!form.valid" />
38 </div>
39 </form>
40 </div>
41
42</ng-template>
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.scss b/client/src/app/+admin/follows/following-list/follow-modal.component.scss
new file mode 100644
index 000000000..9621a566f
--- /dev/null
+++ b/client/src/app/+admin/follows/following-list/follow-modal.component.scss
@@ -0,0 +1,3 @@
1textarea {
2 height: 200px;
3}
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts
new file mode 100644
index 000000000..dc6909200
--- /dev/null
+++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts
@@ -0,0 +1,69 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core'
3import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
5import { InstanceFollowService } from '@app/shared/shared-instance'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
8
9@Component({
10 selector: 'my-follow-modal',
11 templateUrl: './follow-modal.component.html',
12 styleUrls: [ './follow-modal.component.scss' ]
13})
14export class FollowModalComponent extends FormReactive implements OnInit {
15 @ViewChild('modal', { static: true }) modal: NgbModal
16
17 @Output() newFollow = new EventEmitter<void>()
18
19 placeholder = 'example.com\nchocobozzz@example.com\nchocobozzz_channel@example.com'
20
21 private openedModal: NgbModalRef
22
23 constructor (
24 protected formValidatorService: FormValidatorService,
25 private modalService: NgbModal,
26 private followService: InstanceFollowService,
27 private notifier: Notifier
28 ) {
29 super()
30 }
31
32 ngOnInit () {
33 this.buildForm({
34 hostsOrHandles: UNIQUE_HOSTS_OR_HANDLE_VALIDATOR
35 })
36 }
37
38 openModal () {
39 this.openedModal = this.modalService.open(this.modal, { centered: true })
40 }
41
42 hide () {
43 this.openedModal.close()
44 }
45
46 submit () {
47 this.addFollowing()
48
49 this.form.reset()
50 this.hide()
51 }
52
53 httpEnabled () {
54 return window.location.protocol === 'https:'
55 }
56
57 private async addFollowing () {
58 const hostsOrHandles = splitAndGetNotEmpty(this.form.value['hostsOrHandles'])
59
60 this.followService.follow(hostsOrHandles).subscribe(
61 () => {
62 this.notifier.success($localize`Follow request(s) sent!`)
63 this.newFollow.emit()
64 },
65
66 err => this.notifier.error(err.message)
67 )
68 }
69}
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index e7c0c9088..75b0efca8 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -1,6 +1,6 @@
1<h1> 1<h1>
2 <my-global-icon iconName="following" aria-hidden="true"></my-global-icon> 2 <my-global-icon iconName="following" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Instances you follow</ng-container> 3 <ng-container i18n>Your instance subscriptions</ng-container>
4</h1> 4</h1>
5 5
6<p-table 6<p-table
@@ -13,9 +13,9 @@
13 <ng-template pTemplate="caption"> 13 <ng-template pTemplate="caption">
14 <div class="caption"> 14 <div class="caption">
15 <div class="left-buttons"> 15 <div class="left-buttons">
16 <a class="follow-button" (click)="addDomainsToFollow()" (key.enter)="addDomainsToFollow()"> 16 <a class="follow-button" (click)="openFollowModal()" (key.enter)="openFollowModal()">
17 <my-global-icon iconName="following" aria-hidden="true"></my-global-icon> 17 <my-global-icon iconName="following" aria-hidden="true"></my-global-icon>
18 <ng-container i18n>Follow instances</ng-container> 18 <ng-container i18n>Follow</ng-container>
19 </a> 19 </a>
20 </div> 20 </div>
21 21
@@ -28,7 +28,7 @@
28 <ng-template pTemplate="header"> 28 <ng-template pTemplate="header">
29 <tr> 29 <tr>
30 <th style="width: 150px;" i18n>Action</th> 30 <th style="width: 150px;" i18n>Action</th>
31 <th i18n>Host</th> 31 <th i18n>Following</th>
32 <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> 32 <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
33 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 33 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
34 <th style="width: 160px;" i18n pSortableColumn="redundancyAllowed">Redundancy allowed <p-sortIcon field="redundancyAllowed"></p-sortIcon></th> 34 <th style="width: 160px;" i18n pSortableColumn="redundancyAllowed">Redundancy allowed <p-sortIcon field="redundancyAllowed"></p-sortIcon></th>
@@ -41,8 +41,8 @@
41 <my-delete-button label="Unfollow" i18n-label (click)="removeFollowing(follow)"></my-delete-button> 41 <my-delete-button label="Unfollow" i18n-label (click)="removeFollowing(follow)"></my-delete-button>
42 </td> 42 </td>
43 <td> 43 <td>
44 <a [href]="'https://' + follow.following.host" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> 44 <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
45 {{ follow.following.host }} 45 {{ follow.following.name + '@' + follow.following.host }}
46 <span class="glyphicon glyphicon-new-window"></span> 46 <span class="glyphicon glyphicon-new-window"></span>
47 </a> 47 </a>
48 </td> 48 </td>
@@ -57,6 +57,7 @@
57 <td>{{ follow.createdAt | date: 'short' }}</td> 57 <td>{{ follow.createdAt | date: 'short' }}</td>
58 <td> 58 <td>
59 <my-redundancy-checkbox 59 <my-redundancy-checkbox
60 *ngIf="isInstanceFollowing(follow)"
60 [host]="follow.following.host" [redundancyAllowed]="follow.following.hostRedundancyAllowed" 61 [host]="follow.following.host" [redundancyAllowed]="follow.following.hostRedundancyAllowed"
61 ></my-redundancy-checkbox> 62 ></my-redundancy-checkbox>
62 </td> 63 </td>
@@ -75,10 +76,4 @@
75 </ng-template> 76 </ng-template>
76</p-table> 77</p-table>
77 78
78<my-batch-domains-modal #batchDomainsModal i18n-action action="Follow domains" (domains)="addFollowing($event)"> 79<my-follow-modal #followModal></my-follow-modal>
79 <ng-container ngProjectAs="warning">
80 <div i18n *ngIf="httpEnabled() === false" class="alert alert-warning">
81 It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers.
82 </div>
83 </ng-container>
84</my-batch-domains-modal>
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index b63fe08c0..ba62dfa23 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -4,13 +4,14 @@ import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
4import { InstanceFollowService } from '@app/shared/shared-instance' 4import { InstanceFollowService } from '@app/shared/shared-instance'
5import { BatchDomainsModalComponent } from '@app/shared/shared-moderation' 5import { BatchDomainsModalComponent } from '@app/shared/shared-moderation'
6import { ActorFollow } from '@shared/models' 6import { ActorFollow } from '@shared/models'
7import { FollowModalComponent } from './follow-modal.component'
7 8
8@Component({ 9@Component({
9 templateUrl: './following-list.component.html', 10 templateUrl: './following-list.component.html',
10 styleUrls: [ '../follows.component.scss', './following-list.component.scss' ] 11 styleUrls: [ '../follows.component.scss', './following-list.component.scss' ]
11}) 12})
12export class FollowingListComponent extends RestTable implements OnInit { 13export class FollowingListComponent extends RestTable implements OnInit {
13 @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent 14 @ViewChild('followModal') followModal: FollowModalComponent
14 15
15 following: ActorFollow[] = [] 16 following: ActorFollow[] = []
16 totalRecords = 0 17 totalRecords = 0
@@ -33,23 +34,12 @@ export class FollowingListComponent extends RestTable implements OnInit {
33 return 'FollowingListComponent' 34 return 'FollowingListComponent'
34 } 35 }
35 36
36 addDomainsToFollow () { 37 openFollowModal () {
37 this.batchDomainsModal.openModal() 38 this.followModal.openModal()
38 } 39 }
39 40
40 httpEnabled () { 41 isInstanceFollowing (follow: ActorFollow) {
41 return window.location.protocol === 'https:' 42 return follow.following.name === 'peertube'
42 }
43
44 async addFollowing (hosts: string[]) {
45 this.followService.follow(hosts).subscribe(
46 () => {
47 this.notifier.success($localize`Follow request(s) sent!`)
48 this.reloadData()
49 },
50
51 err => this.notifier.error(err.message)
52 )
53 } 43 }
54 44
55 async removeFollowing (follow: ActorFollow) { 45 async removeFollowing (follow: ActorFollow) {
diff --git a/client/src/app/+admin/follows/following-list/index.ts b/client/src/app/+admin/follows/following-list/index.ts
index a70d46a7e..88be0ed4c 100644
--- a/client/src/app/+admin/follows/following-list/index.ts
+++ b/client/src/app/+admin/follows/following-list/index.ts
@@ -1 +1,2 @@
1export * from './follow-modal.component'
1export * from './following-list.component' 2export * from './following-list.component'
diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts
index cd70daf77..3843b42b5 100644
--- a/client/src/app/+admin/follows/follows.routes.ts
+++ b/client/src/app/+admin/follows/follows.routes.ts
@@ -25,7 +25,7 @@ export const FollowsRoutes: Routes = [
25 component: FollowingListComponent, 25 component: FollowingListComponent,
26 data: { 26 data: {
27 meta: { 27 meta: {
28 title: $localize`Following list` 28 title: $localize`Following`
29 } 29 }
30 } 30 }
31 }, 31 },
@@ -34,7 +34,7 @@ export const FollowsRoutes: Routes = [
34 component: FollowersListComponent, 34 component: FollowersListComponent,
35 data: { 35 data: {
36 meta: { 36 meta: {
37 title: $localize`Followers list` 37 title: $localize`Followers`
38 } 38 }
39 } 39 }
40 }, 40 },
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html
index 5f5b0f565..27793ff0c 100644
--- a/client/src/app/+login/login.component.html
+++ b/client/src/app/+login/login.component.html
@@ -28,6 +28,10 @@
28 <div *ngIf="formErrors.username" class="form-error"> 28 <div *ngIf="formErrors.username" class="form-error">
29 {{ formErrors.username }} 29 {{ formErrors.username }}
30 </div> 30 </div>
31
32 <div *ngIf="hasUsernameUppercase()" i18n class="form-warning">
33 ⚠️ Most email addresses do not include capital letters.
34 </div>
31 </div> 35 </div>
32 36
33 <div class="form-group"> 37 <div class="form-group">
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts
index d8ad49081..9731383af 100644
--- a/client/src/app/+login/login.component.ts
+++ b/client/src/app/+login/login.component.ts
@@ -141,6 +141,10 @@ The link will expire within 1 hour.`
141 this.accordion = instanceAboutAccordion.accordion 141 this.accordion = instanceAboutAccordion.accordion
142 } 142 }
143 143
144 hasUsernameUppercase () {
145 return this.form.value['username'].match(/[A-Z]/)
146 }
147
144 private loadExternalAuthToken (username: string, token: string) { 148 private loadExternalAuthToken (username: string, token: string) {
145 this.isAuthenticatedWithExternalAuth = true 149 this.isAuthenticatedWithExternalAuth = true
146 150
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
index b3265210f..433475f66 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
@@ -1,3 +1,5 @@
1import { of } from 'rxjs'
2import { switchMap } from 'rxjs/operators'
1import { Component, OnInit } from '@angular/core' 3import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router' 4import { Router } from '@angular/router'
3import { AuthService, Notifier } from '@app/core' 5import { AuthService, Notifier } from '@app/core'
@@ -9,11 +11,8 @@ import {
9} from '@app/shared/form-validators/video-channel-validators' 11} from '@app/shared/form-validators/video-channel-validators'
10import { FormValidatorService } from '@app/shared/shared-forms' 12import { FormValidatorService } from '@app/shared/shared-forms'
11import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' 13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
12import { VideoChannelCreate } from '@shared/models' 14import { HttpStatusCode, VideoChannelCreate } from '@shared/models'
13import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
14import { MyVideoChannelEdit } from './my-video-channel-edit' 15import { MyVideoChannelEdit } from './my-video-channel-edit'
15import { switchMap } from 'rxjs/operators'
16import { of } from 'rxjs'
17 16
18@Component({ 17@Component({
19 templateUrl: './my-video-channel-edit.component.html', 18 templateUrl: './my-video-channel-edit.component.html',
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
index 67b3ee496..b6a2f592d 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
@@ -45,9 +45,9 @@ export class MyVideoChannelsComponent {
45It will delete ${videoChannel.videosCount} videos uploaded in this channel, and you will not be able to create another 45It will delete ${videoChannel.videosCount} videos uploaded in this channel, and you will not be able to create another
46channel with the same name (${videoChannel.name})!`, 46channel with the same name (${videoChannel.name})!`,
47 47
48 $localize`Please type the display name of the video channel (${videoChannel.displayName}) to confirm`, 48 $localize`Please type the name of the video channel (${videoChannel.name}) to confirm`,
49 49
50 videoChannel.displayName, 50 videoChannel.name,
51 51
52 $localize`Delete` 52 $localize`Delete`
53 ) 53 )
diff --git a/client/src/app/+page-not-found/page-not-found.component.ts b/client/src/app/+page-not-found/page-not-found.component.ts
index 639e5db78..10645a634 100644
--- a/client/src/app/+page-not-found/page-not-found.component.ts
+++ b/client/src/app/+page-not-found/page-not-found.component.ts
@@ -1,7 +1,8 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { Title } from '@angular/platform-browser' 2import { Title } from '@angular/platform-browser'
3import { Router } from '@angular/router' 3import { Router } from '@angular/router'
4import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 4import { HttpStatusCode } from '@shared/models'
5
5@Component({ 6@Component({
6 selector: 'my-page-not-found', 7 selector: 'my-page-not-found',
7 templateUrl: './page-not-found.component.html', 8 templateUrl: './page-not-found.component.html',
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts
index 3833d9c54..6479644f1 100644
--- a/client/src/app/+video-channels/video-channels.component.ts
+++ b/client/src/app/+video-channels/video-channels.component.ts
@@ -7,7 +7,7 @@ import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService }
7import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' 7import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
8import { SupportModalComponent } from '@app/shared/shared-support-modal' 8import { SupportModalComponent } from '@app/shared/shared-support-modal'
9import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' 9import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
10import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 10import { HttpStatusCode } from '@shared/models'
11 11
12@Component({ 12@Component({
13 templateUrl: './video-channels.component.html', 13 templateUrl: './video-channels.component.html',
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
index 627de33c0..e9420fe62 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
@@ -7,8 +7,7 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
7import { FormValidatorService } from '@app/shared/shared-forms' 7import { FormValidatorService } from '@app/shared/shared-forms'
8import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' 8import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
9import { LoadingBarService } from '@ngx-loading-bar/core' 9import { LoadingBarService } from '@ngx-loading-bar/core'
10import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 10import { HttpStatusCode, VideoPrivacy } from '@shared/models'
11import { VideoPrivacy } from '@shared/models'
12import { UploaderXFormData } from './uploaderx-form-data' 11import { UploaderXFormData } from './uploaderx-form-data'
13import { VideoSend } from './video-send' 12import { VideoSend } from './video-send'
14 13
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index d078844c3..7460ae3fc 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -21,8 +21,15 @@ import { isXPercentInViewport, scrollToTop } from '@app/helpers'
21import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main' 21import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main'
22import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' 22import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
23import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' 23import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
24import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 24import {
25import { HTMLServerConfig, PeerTubeProblemDocument, ServerErrorCode, VideoCaption, VideoPrivacy, VideoState } from '@shared/models' 25 HTMLServerConfig,
26 HttpStatusCode,
27 PeerTubeProblemDocument,
28 ServerErrorCode,
29 VideoCaption,
30 VideoPrivacy,
31 VideoState
32} from '@shared/models'
26import { cleanupVideoWatch, getStoredTheater, getStoredVideoWatchHistory } from '../../../assets/player/peertube-player-local-storage' 33import { cleanupVideoWatch, getStoredTheater, getStoredVideoWatchHistory } from '../../../assets/player/peertube-player-local-storage'
27import { 34import {
28 CustomizationOptions, 35 CustomizationOptions,
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index cdf13186b..60bd72c60 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -6,12 +6,11 @@ import { Injectable } from '@angular/core'
6import { Router } from '@angular/router' 6import { Router } from '@angular/router'
7import { Notifier } from '@app/core/notification/notifier.service' 7import { Notifier } from '@app/core/notification/notifier.service'
8import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' 8import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index'
9import { MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' 9import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
10import { environment } from '../../../environments/environment' 10import { environment } from '../../../environments/environment'
11import { RestExtractor } from '../rest/rest-extractor.service' 11import { RestExtractor } from '../rest/rest-extractor.service'
12import { AuthStatus } from './auth-status.model' 12import { AuthStatus } from './auth-status.model'
13import { AuthUser } from './auth-user.model' 13import { AuthUser } from './auth-user.model'
14import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
15 14
16interface UserLoginWithUsername extends UserLogin { 15interface UserLoginWithUsername extends UserLogin {
17 access_token: string 16 access_token: string
diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts
index 08ab49512..2a926e68f 100644
--- a/client/src/app/core/rest/rest-extractor.service.ts
+++ b/client/src/app/core/rest/rest-extractor.service.ts
@@ -2,8 +2,7 @@ import { throwError as observableThrowError } from 'rxjs'
2import { Injectable } from '@angular/core' 2import { Injectable } from '@angular/core'
3import { Router } from '@angular/router' 3import { Router } from '@angular/router'
4import { dateToHuman } from '@app/helpers' 4import { dateToHuman } from '@app/helpers'
5import { ResultList } from '@shared/models' 5import { HttpStatusCode, ResultList } from '@shared/models'
6import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
7 6
8@Injectable() 7@Injectable()
9export class RestExtractor { 8export class RestExtractor {
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts
index 94f6def26..edcaf50e0 100644
--- a/client/src/app/helpers/utils.ts
+++ b/client/src/app/helpers/utils.ts
@@ -3,7 +3,7 @@ import { SelectChannelItem } from 'src/types/select-options-item.model'
3import { DatePipe } from '@angular/common' 3import { DatePipe } from '@angular/common'
4import { HttpErrorResponse } from '@angular/common/http' 4import { HttpErrorResponse } from '@angular/common/http'
5import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
6import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '@shared/models'
7import { environment } from '../../environments/environment' 7import { environment } from '../../environments/environment'
8import { AuthService } from '../core/auth' 8import { AuthService } from '../core/auth'
9 9
diff --git a/client/src/app/shared/form-validators/batch-domains-validators.ts b/client/src/app/shared/form-validators/batch-domains-validators.ts
deleted file mode 100644
index 423d1337f..000000000
--- a/client/src/app/shared/form-validators/batch-domains-validators.ts
+++ /dev/null
@@ -1,60 +0,0 @@
1import { AbstractControl, FormControl, ValidatorFn, Validators } from '@angular/forms'
2import { BuildFormValidator } from './form-validator.model'
3import { validateHost } from './host'
4
5export function getNotEmptyHosts (hosts: string) {
6 return hosts
7 .split('\n')
8 .filter((host: string) => host && host.length !== 0) // Eject empty hosts
9}
10
11const validDomains: ValidatorFn = (control: FormControl) => {
12 if (!control.value) return null
13
14 const newHostsErrors = []
15 const hosts = getNotEmptyHosts(control.value)
16
17 for (const host of hosts) {
18 if (validateHost(host) === false) {
19 newHostsErrors.push($localize`${host} is not valid`)
20 }
21 }
22
23 /* Is not valid. */
24 if (newHostsErrors.length !== 0) {
25 return {
26 'validDomains': {
27 reason: 'invalid',
28 value: newHostsErrors.join('. ') + '.'
29 }
30 }
31 }
32
33 /* Is valid. */
34 return null
35}
36
37const isHostsUnique: ValidatorFn = (control: AbstractControl) => {
38 if (!control.value) return null
39
40 const hosts = getNotEmptyHosts(control.value)
41
42 if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) {
43 return null
44 } else {
45 return {
46 'uniqueDomains': {
47 reason: 'invalid'
48 }
49 }
50 }
51}
52
53export const DOMAINS_VALIDATOR: BuildFormValidator = {
54 VALIDATORS: [Validators.required, validDomains, isHostsUnique],
55 MESSAGES: {
56 'required': $localize`Domain is required.`,
57 'validDomains': $localize`Domains entered are invalid.`,
58 'uniqueDomains': $localize`Domains entered contain duplicates.`
59 }
60}
diff --git a/client/src/app/shared/form-validators/host-validators.ts b/client/src/app/shared/form-validators/host-validators.ts
new file mode 100644
index 000000000..d750113ef
--- /dev/null
+++ b/client/src/app/shared/form-validators/host-validators.ts
@@ -0,0 +1,105 @@
1import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'
2import { BuildFormValidator } from './form-validator.model'
3
4function validateHost (value: string) {
5 // Thanks to http://stackoverflow.com/a/106223
6 const HOST_REGEXP = new RegExp(
7 '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
8 )
9
10 return HOST_REGEXP.test(value)
11}
12
13function validateHandle (value: string) {
14 if (!value) return false
15
16 return value.includes('@')
17}
18
19const validHosts: ValidatorFn = (control: AbstractControl) => {
20 if (!control.value) return null
21
22 const errors = []
23 const hosts = splitAndGetNotEmpty(control.value)
24
25 for (const host of hosts) {
26 if (validateHost(host) === false) {
27 errors.push($localize`${host} is not valid`)
28 }
29 }
30
31 // valid
32 if (errors.length === 0) return null
33
34 return {
35 'validHosts': {
36 reason: 'invalid',
37 value: errors.join('. ') + '.'
38 }
39 }
40}
41
42const validHostsOrHandles: ValidatorFn = (control: AbstractControl) => {
43 if (!control.value) return null
44
45 const errors = []
46 const lines = splitAndGetNotEmpty(control.value)
47
48 for (const line of lines) {
49 if (validateHost(line) === false && validateHandle(line) === false) {
50 errors.push($localize`${line} is not valid`)
51 }
52 }
53
54 // valid
55 if (errors.length === 0) return null
56
57 return {
58 'validHostsOrHandles': {
59 reason: 'invalid',
60 value: errors.join('. ') + '.'
61 }
62 }
63}
64
65// ---------------------------------------------------------------------------
66
67export function splitAndGetNotEmpty (value: string) {
68 return value
69 .split('\n')
70 .filter(line => line && line.length !== 0) // Eject empty hosts
71}
72
73export const unique: ValidatorFn = (control: AbstractControl) => {
74 if (!control.value) return null
75
76 const hosts = splitAndGetNotEmpty(control.value)
77
78 if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) {
79 return null
80 }
81
82 return {
83 'unique': {
84 reason: 'invalid'
85 }
86 }
87}
88
89export const UNIQUE_HOSTS_VALIDATOR: BuildFormValidator = {
90 VALIDATORS: [ Validators.required, validHosts, unique ],
91 MESSAGES: {
92 'required': $localize`Domain is required.`,
93 'validHosts': $localize`Hosts entered are invalid.`,
94 'unique': $localize`Hosts entered contain duplicates.`
95 }
96}
97
98export const UNIQUE_HOSTS_OR_HANDLE_VALIDATOR: BuildFormValidator = {
99 VALIDATORS: [ Validators.required, validHostsOrHandles, unique ],
100 MESSAGES: {
101 'required': $localize`Domain is required.`,
102 'validHostsOrHandles': $localize`Hosts or handles are invalid.`,
103 'unique': $localize`Hosts or handles contain duplicates.`
104 }
105}
diff --git a/client/src/app/shared/form-validators/host.ts b/client/src/app/shared/form-validators/host.ts
deleted file mode 100644
index c18a35f9b..000000000
--- a/client/src/app/shared/form-validators/host.ts
+++ /dev/null
@@ -1,8 +0,0 @@
1export function validateHost (value: string) {
2 // Thanks to http://stackoverflow.com/a/106223
3 const HOST_REGEXP = new RegExp(
4 '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
5 )
6
7 return HOST_REGEXP.test(value)
8}
diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts
index f621f03a4..0b605719c 100644
--- a/client/src/app/shared/form-validators/index.ts
+++ b/client/src/app/shared/form-validators/index.ts
@@ -1,7 +1,6 @@
1export * from './form-validator.model' 1export * from './form-validator.model'
2export * from './host'
3 2
4// Don't re export const variables because webpack 4 cannot do tree shaking with them 3// Don't re export const variables because webpack cannot do tree shaking with them
5// export * from './abuse-validators' 4// export * from './abuse-validators'
6// export * from './batch-domains-validators' 5// export * from './batch-domains-validators'
7// export * from './custom-config-validators' 6// export * from './custom-config-validators'
diff --git a/client/src/app/shared/shared-instance/instance-follow.service.ts b/client/src/app/shared/shared-instance/instance-follow.service.ts
index e52660140..af44020cf 100644
--- a/client/src/app/shared/shared-instance/instance-follow.service.ts
+++ b/client/src/app/shared/shared-instance/instance-follow.service.ts
@@ -4,7 +4,7 @@ import { catchError, map } from 'rxjs/operators'
4import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
6import { RestExtractor, RestPagination, RestService } from '@app/core' 6import { RestExtractor, RestPagination, RestService } from '@app/core'
7import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/models' 7import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models'
8import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
9 9
10@Injectable() 10@Injectable()
@@ -64,9 +64,10 @@ export class InstanceFollowService {
64 ) 64 )
65 } 65 }
66 66
67 follow (notEmptyHosts: string[]) { 67 follow (hostsOrHandles: string[]) {
68 const body = { 68 const body: ServerFollowCreate = {
69 hosts: notEmptyHosts 69 handles: hostsOrHandles.filter(v => v.includes('@')),
70 hosts: hostsOrHandles.filter(v => !v.includes('@'))
70 } 71 }
71 72
72 return this.authHttp.post(InstanceFollowService.BASE_APPLICATION_URL + '/following', body) 73 return this.authHttp.post(InstanceFollowService.BASE_APPLICATION_URL + '/following', body)
@@ -77,7 +78,9 @@ export class InstanceFollowService {
77 } 78 }
78 79
79 unfollow (follow: ActorFollow) { 80 unfollow (follow: ActorFollow) {
80 return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host) 81 const handle = follow.following.name + '@' + follow.following.host
82
83 return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle)
81 .pipe( 84 .pipe(
82 map(this.restExtractor.extractDataBool), 85 map(this.restExtractor.extractDataBool),
83 catchError(res => this.restExtractor.handleError(res)) 86 catchError(res => this.restExtractor.handleError(res))
diff --git a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
index 5bcad36d0..a75c8a25c 100644
--- a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
+++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
@@ -1,11 +1,11 @@
1import { Observable, of, throwError as observableThrowError } from 'rxjs' 1import { Observable, of, throwError as observableThrowError } from 'rxjs'
2import { catchError, switchMap } from 'rxjs/operators' 2import { catchError, switchMap } from 'rxjs/operators'
3import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http' 3import { HTTP_INTERCEPTORS, HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'
4import { Injectable, Injector } from '@angular/core' 4import { Injectable, Injector } from '@angular/core'
5import { AuthService } from '@app/core/auth/auth.service'
6import { Router } from '@angular/router' 5import { Router } from '@angular/router'
7import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 6import { AuthService } from '@app/core/auth/auth.service'
8import { OAuth2ErrorCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models/server' 7import { HttpStatusCode } from '@shared/models'
8import { OAuth2ErrorCode, PeerTubeProblemDocument } from '@shared/models/server'
9 9
10@Injectable() 10@Injectable()
11export class AuthInterceptor implements HttpInterceptor { 11export class AuthInterceptor implements HttpInterceptor {
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.html b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html
index 6a3c65721..8306a96bc 100644
--- a/client/src/app/shared/shared-moderation/batch-domains-modal.component.html
+++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html
@@ -1,6 +1,6 @@
1<ng-template #modal> 1<ng-template #modal>
2 <div class="modal-header"> 2 <div class="modal-header">
3 <h4 i18n class="modal-title">{{ action }}</h4> 3 <h4 class="modal-title">{{ action }}</h4>
4 4
5 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> 5 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
6 </div> 6 </div>
@@ -11,15 +11,15 @@
11 <label i18n for="hosts">1 host (without "http://") per line</label> 11 <label i18n for="hosts">1 host (without "http://") per line</label>
12 12
13 <textarea 13 <textarea
14 [placeholder]="placeholder" formControlName="domains" type="text" id="hosts" name="hosts" 14 [placeholder]="placeholder" formControlName="hosts" type="text" id="hosts" name="hosts"
15 class="form-control" [ngClass]="{ 'input-error': formErrors['domains'] }" ngbAutofocus 15 class="form-control" [ngClass]="{ 'input-error': formErrors['hosts'] }" ngbAutofocus
16 ></textarea> 16 ></textarea>
17 17
18 <div *ngIf="formErrors.domains" class="form-error"> 18 <div *ngIf="formErrors.hosts" class="form-error">
19 {{ formErrors.domains }} 19 {{ formErrors.hosts }}
20 20
21 <div *ngIf="form.controls['domains'].errors.validDomains"> 21 <div *ngIf="form.controls['hosts'].errors.validHosts">
22 {{ form.controls['domains'].errors.validDomains.value }} 22 {{ form.controls['hosts'].errors.validHosts.value }}
23 </div> 23 </div>
24 </div> 24 </div>
25 </div> 25 </div>
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
index 6edbb6023..20be728f6 100644
--- a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
+++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
@@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angu
2import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 2import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 4import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
5import { DOMAINS_VALIDATOR, getNotEmptyHosts } from '../form-validators/batch-domains-validators' 5import { splitAndGetNotEmpty, UNIQUE_HOSTS_VALIDATOR } from '../form-validators/host-validators'
6 6
7@Component({ 7@Component({
8 selector: 'my-batch-domains-modal', 8 selector: 'my-batch-domains-modal',
@@ -28,7 +28,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit {
28 if (!this.action) this.action = $localize`Process domains` 28 if (!this.action) this.action = $localize`Process domains`
29 29
30 this.buildForm({ 30 this.buildForm({
31 domains: DOMAINS_VALIDATOR 31 hosts: UNIQUE_HOSTS_VALIDATOR
32 }) 32 })
33 } 33 }
34 34
@@ -41,9 +41,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit {
41 } 41 }
42 42
43 submit () { 43 submit () {
44 this.domains.emit( 44 this.domains.emit(splitAndGetNotEmpty(this.form.controls['hosts'].value))
45 getNotEmptyHosts(this.form.controls['domains'].value)
46 )
47 this.form.reset() 45 this.form.reset()
48 this.hide() 46 this.hide()
49 } 47 }
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 30d487b11..bd834db70 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -123,12 +123,16 @@ code {
123 vertical-align: middle; 123 vertical-align: middle;
124} 124}
125 125
126.form-error { 126.form-error,
127.form-warning {
127 display: block; 128 display: block;
128 color: $red;
129 margin-top: 5px; 129 margin-top: 5px;
130} 130}
131 131
132.form-error {
133 color: $red;
134}
135
132.input-error, 136.input-error,
133my-input-toggle-hidden ::ng-deep input { 137my-input-toggle-hidden ::ng-deep input {
134 border-color: $red !important; 138 border-color: $red !important;
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index e59d8b940..97437ce45 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -1,9 +1,9 @@
1import './embed.scss' 1import './embed.scss'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { peertubeTranslate } from '../../../../shared/core-utils/i18n' 3import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
4import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
5import { 4import {
6 HTMLServerConfig, 5 HTMLServerConfig,
6 HttpStatusCode,
7 OAuth2ErrorCode, 7 OAuth2ErrorCode,
8 ResultList, 8 ResultList,
9 UserRefreshToken, 9 UserRefreshToken,