diff options
Diffstat (limited to 'client/src/app')
220 files changed, 3173 insertions, 1601 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index 5970cac01..8c700752e 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -1,39 +1,52 @@ | |||
1 | <div i18n class="about-instance-title"> | 1 | <div class="row"> |
2 | About {{ instanceName }} instance | 2 | <div class="col-md-12 col-xl-6"> |
3 | </div> | 3 | <div class="about-instance-title"> |
4 | <div i18n>About {{ instanceName }} instance</div> | ||
4 | 5 | ||
5 | <div class="short-description"> | 6 | <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div> |
6 | <div>{{ shortDescription }}</div> | 7 | </div> |
7 | </div> | ||
8 | 8 | ||
9 | <div class="description"> | 9 | <div class="short-description"> |
10 | <div i18n class="section-title">Description</div> | 10 | <div>{{ shortDescription }}</div> |
11 | </div> | ||
11 | 12 | ||
12 | <div [innerHTML]="descriptionHTML"></div> | 13 | <div class="description"> |
13 | </div> | 14 | <div i18n class="section-title">Description</div> |
14 | 15 | ||
15 | <div class="terms" id="terms-section"> | 16 | <div [innerHTML]="descriptionHTML"></div> |
16 | <div i18n class="section-title">Terms</div> | 17 | </div> |
17 | 18 | ||
18 | <div [innerHTML]="termsHTML"></div> | 19 | <div class="terms" id="terms-section"> |
19 | </div> | 20 | <div i18n class="section-title">Terms</div> |
20 | 21 | ||
21 | <div class="signup"> | 22 | <div [innerHTML]="termsHTML"></div> |
22 | <div i18n class="section-title">Signup</div> | 23 | </div> |
23 | 24 | ||
24 | <div *ngIf="isSignupAllowed"> | 25 | <div class="signup"> |
25 | <ng-container i18n>User registration is allowed and</ng-container> | 26 | <div i18n class="section-title">Signup</div> |
26 | 27 | ||
27 | <ng-container i18n *ngIf="userVideoQuota !== -1"> | 28 | <div *ngIf="isSignupAllowed"> |
28 | this instance provides a baseline quota of {{ userVideoQuota | bytes: 0 }} space for the videos of its users. | 29 | <ng-container i18n>User registration is allowed and</ng-container> |
29 | </ng-container> | ||
30 | 30 | ||
31 | <ng-container i18n *ngIf="userVideoQuota === -1"> | 31 | <ng-container i18n *ngIf="userVideoQuota !== -1"> |
32 | this instance provides unlimited space for the videos of its users. | 32 | this instance provides a baseline quota of {{ userVideoQuota | bytes: 0 }} space for the videos of its users. |
33 | </ng-container> | 33 | </ng-container> |
34 | |||
35 | <ng-container i18n *ngIf="userVideoQuota === -1"> | ||
36 | this instance provides unlimited space for the videos of its users. | ||
37 | </ng-container> | ||
38 | </div> | ||
39 | |||
40 | <div i18n *ngIf="isSignupAllowed === false"> | ||
41 | User registration is currently not allowed. | ||
42 | </div> | ||
43 | </div> | ||
34 | </div> | 44 | </div> |
35 | 45 | ||
36 | <div i18n *ngIf="isSignupAllowed === false"> | 46 | <div class="col-md-12 col-xl-6"> |
37 | User registration is currently not allowed. | 47 | <label>Features found on this instance</label> |
48 | <my-instance-features-table></my-instance-features-table> | ||
38 | </div> | 49 | </div> |
39 | </div> \ No newline at end of file | 50 | </div> |
51 | |||
52 | <my-contact-admin-modal #contactAdminModal></my-contact-admin-modal> | ||
diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss index b451e85aa..75cf57322 100644 --- a/client/src/app/+about/about-instance/about-instance.component.scss +++ b/client/src/app/+about/about-instance/about-instance.component.scss | |||
@@ -2,9 +2,19 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .about-instance-title { | 4 | .about-instance-title { |
5 | font-size: 20px; | 5 | display: flex; |
6 | font-weight: bold; | 6 | justify-content: space-between; |
7 | margin-bottom: 15px; | 7 | |
8 | & > div { | ||
9 | font-size: 20px; | ||
10 | font-weight: bold; | ||
11 | margin-bottom: 15px; | ||
12 | } | ||
13 | |||
14 | & > .contact-admin { | ||
15 | @include peertube-button; | ||
16 | @include orange-button; | ||
17 | } | ||
8 | } | 18 | } |
9 | 19 | ||
10 | .section-title { | 20 | .section-title { |
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index 354f52ce7..a1b30fa8c 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts | |||
@@ -1,23 +1,26 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { MarkdownService } from '@app/videos/shared' | ||
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | ||
5 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
6 | import { MarkdownService } from '@app/shared/renderer' | ||
6 | 7 | ||
7 | @Component({ | 8 | @Component({ |
8 | selector: 'my-about-instance', | 9 | selector: 'my-about-instance', |
9 | templateUrl: './about-instance.component.html', | 10 | templateUrl: './about-instance.component.html', |
10 | styleUrls: [ './about-instance.component.scss' ] | 11 | styleUrls: [ './about-instance.component.scss' ] |
11 | }) | 12 | }) |
12 | |||
13 | export class AboutInstanceComponent implements OnInit { | 13 | export class AboutInstanceComponent implements OnInit { |
14 | @ViewChild('contactAdminModal') contactAdminModal: ContactAdminModalComponent | ||
15 | |||
14 | shortDescription = '' | 16 | shortDescription = '' |
15 | descriptionHTML = '' | 17 | descriptionHTML = '' |
16 | termsHTML = '' | 18 | termsHTML = '' |
17 | 19 | ||
18 | constructor ( | 20 | constructor ( |
19 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
20 | private serverService: ServerService, | 22 | private serverService: ServerService, |
23 | private instanceService: InstanceService, | ||
21 | private markdownService: MarkdownService, | 24 | private markdownService: MarkdownService, |
22 | private i18n: I18n | 25 | private i18n: I18n |
23 | ) {} | 26 | ) {} |
@@ -34,8 +37,12 @@ export class AboutInstanceComponent implements OnInit { | |||
34 | return this.serverService.getConfig().signup.allowed | 37 | return this.serverService.getConfig().signup.allowed |
35 | } | 38 | } |
36 | 39 | ||
40 | get isContactFormEnabled () { | ||
41 | return this.serverService.getConfig().email.enabled && this.serverService.getConfig().contactForm.enabled | ||
42 | } | ||
43 | |||
37 | ngOnInit () { | 44 | ngOnInit () { |
38 | this.serverService.getAbout() | 45 | this.instanceService.getAbout() |
39 | .subscribe( | 46 | .subscribe( |
40 | res => { | 47 | res => { |
41 | this.shortDescription = res.instance.shortDescription | 48 | this.shortDescription = res.instance.shortDescription |
@@ -43,8 +50,12 @@ export class AboutInstanceComponent implements OnInit { | |||
43 | this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms) | 50 | this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms) |
44 | }, | 51 | }, |
45 | 52 | ||
46 | err => this.notificationsService.error(this.i18n('Error getting about from server'), err) | 53 | () => this.notifier.error(this.i18n('Cannot get about information from server')) |
47 | ) | 54 | ) |
48 | } | 55 | } |
49 | 56 | ||
57 | openContactModal () { | ||
58 | return this.contactAdminModal.show() | ||
59 | } | ||
60 | |||
50 | } | 61 | } |
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.html b/client/src/app/+about/about-instance/contact-admin-modal.component.html new file mode 100644 index 000000000..b2cbd0873 --- /dev/null +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.html | |||
@@ -0,0 +1,50 @@ | |||
1 | <ng-template #modal> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Contact {{ instanceName }} administrator</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <form novalidate [formGroup]="form" (ngSubmit)="sendForm()"> | ||
10 | <div class="form-group"> | ||
11 | <label i18n for="fromName">Your name</label> | ||
12 | <input | ||
13 | type="text" id="fromName" | ||
14 | formControlName="fromName" [ngClass]="{ 'input-error': formErrors.fromName }" | ||
15 | > | ||
16 | <div *ngIf="formErrors.fromName" class="form-error">{{ formErrors.fromName }}</div> | ||
17 | </div> | ||
18 | |||
19 | <div class="form-group"> | ||
20 | <label i18n for="fromEmail">Your email</label> | ||
21 | <input | ||
22 | type="text" id="fromEmail" | ||
23 | formControlName="fromEmail" [ngClass]="{ 'input-error': formErrors['fromEmail'] }" | ||
24 | > | ||
25 | <div *ngIf="formErrors.fromEmail" class="form-error">{{ formErrors.fromEmail }}</div> | ||
26 | </div> | ||
27 | |||
28 | <div class="form-group"> | ||
29 | <label i18n for="body">Your message</label> | ||
30 | <textarea id="body" formControlName="body" [ngClass]="{ 'input-error': formErrors['body'] }"> | ||
31 | </textarea> | ||
32 | <div *ngIf="formErrors.body" class="form-error">{{ formErrors.body }}</div> | ||
33 | </div> | ||
34 | |||
35 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
36 | |||
37 | <div class="form-group inputs"> | ||
38 | <span i18n class="action-button action-button-cancel" (click)="hide()"> | ||
39 | Cancel | ||
40 | </span> | ||
41 | |||
42 | <input | ||
43 | type="submit" i18n-value value="Submit" class="action-button-submit" | ||
44 | [disabled]="!form.valid" | ||
45 | > | ||
46 | </div> | ||
47 | </form> | ||
48 | |||
49 | </div> | ||
50 | </ng-template> | ||
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.scss b/client/src/app/+about/about-instance/contact-admin-modal.component.scss new file mode 100644 index 000000000..260d77888 --- /dev/null +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.scss | |||
@@ -0,0 +1,11 @@ | |||
1 | @import 'variables'; | ||
2 | @import 'mixins'; | ||
3 | |||
4 | input[type=text] { | ||
5 | @include peertube-input-text(340px); | ||
6 | display: block; | ||
7 | } | ||
8 | |||
9 | textarea { | ||
10 | @include peertube-textarea(100%, 200px); | ||
11 | } | ||
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 new file mode 100644 index 000000000..7d79c2215 --- /dev/null +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts | |||
@@ -0,0 +1,77 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | ||
2 | import { Notifier, ServerService } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
7 | import { FormReactive, InstanceValidatorsService } from '@app/shared' | ||
8 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-contact-admin-modal', | ||
12 | templateUrl: './contact-admin-modal.component.html', | ||
13 | styleUrls: [ './contact-admin-modal.component.scss' ] | ||
14 | }) | ||
15 | export class ContactAdminModalComponent extends FormReactive implements OnInit { | ||
16 | @ViewChild('modal') modal: NgbModal | ||
17 | |||
18 | error: string | ||
19 | |||
20 | private openedModal: NgbModalRef | ||
21 | |||
22 | constructor ( | ||
23 | protected formValidatorService: FormValidatorService, | ||
24 | private modalService: NgbModal, | ||
25 | private instanceValidatorsService: InstanceValidatorsService, | ||
26 | private instanceService: InstanceService, | ||
27 | private serverService: ServerService, | ||
28 | private notifier: Notifier, | ||
29 | private i18n: I18n | ||
30 | ) { | ||
31 | super() | ||
32 | } | ||
33 | |||
34 | get instanceName () { | ||
35 | return this.serverService.getConfig().instance.name | ||
36 | } | ||
37 | |||
38 | ngOnInit () { | ||
39 | this.buildForm({ | ||
40 | fromName: this.instanceValidatorsService.FROM_NAME, | ||
41 | fromEmail: this.instanceValidatorsService.FROM_EMAIL, | ||
42 | body: this.instanceValidatorsService.BODY | ||
43 | }) | ||
44 | } | ||
45 | |||
46 | show () { | ||
47 | this.openedModal = this.modalService.open(this.modal, { keyboard: false }) | ||
48 | } | ||
49 | |||
50 | hide () { | ||
51 | this.form.reset() | ||
52 | this.error = undefined | ||
53 | |||
54 | this.openedModal.close() | ||
55 | this.openedModal = null | ||
56 | } | ||
57 | |||
58 | sendForm () { | ||
59 | const fromName = this.form.value['fromName'] | ||
60 | const fromEmail = this.form.value[ 'fromEmail' ] | ||
61 | const body = this.form.value[ 'body' ] | ||
62 | |||
63 | this.instanceService.contactAdministrator(fromEmail, fromName, body) | ||
64 | .subscribe( | ||
65 | () => { | ||
66 | this.notifier.success(this.i18n('Your message has been sent.')) | ||
67 | this.hide() | ||
68 | }, | ||
69 | |||
70 | err => { | ||
71 | this.error = err.status === 403 | ||
72 | ? this.i18n('You already sent this form recently') | ||
73 | : err.message | ||
74 | } | ||
75 | ) | ||
76 | } | ||
77 | } | ||
diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts index ff6e8ef41..9c6b29740 100644 --- a/client/src/app/+about/about.module.ts +++ b/client/src/app/+about/about.module.ts | |||
@@ -5,6 +5,7 @@ import { AboutComponent } from './about.component' | |||
5 | import { SharedModule } from '../shared' | 5 | import { SharedModule } from '../shared' |
6 | import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' | 6 | import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' |
7 | import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' | 7 | import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' |
8 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | ||
8 | 9 | ||
9 | @NgModule({ | 10 | @NgModule({ |
10 | imports: [ | 11 | imports: [ |
@@ -15,7 +16,8 @@ import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertub | |||
15 | declarations: [ | 16 | declarations: [ |
16 | AboutComponent, | 17 | AboutComponent, |
17 | AboutInstanceComponent, | 18 | AboutInstanceComponent, |
18 | AboutPeertubeComponent | 19 | AboutPeertubeComponent, |
20 | ContactAdminModalComponent | ||
19 | ], | 21 | ], |
20 | 22 | ||
21 | exports: [ | 23 | exports: [ |
diff --git a/client/src/app/+accounts/account-about/account-about.component.ts b/client/src/app/+accounts/account-about/account-about.component.ts index 6f3e6caa0..13890a0ee 100644 --- a/client/src/app/+accounts/account-about/account-about.component.ts +++ b/client/src/app/+accounts/account-about/account-about.component.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { Component, OnInit, OnDestroy } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { Account } from '@app/shared/account/account.model' | 2 | import { Account } from '@app/shared/account/account.model' |
3 | import { AccountService } from '@app/shared/account/account.service' | 3 | import { AccountService } from '@app/shared/account/account.service' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { Subscription } from 'rxjs' | 5 | import { Subscription } from 'rxjs' |
6 | import { MarkdownService } from '@app/videos/shared' | 6 | import { MarkdownService } from '@app/shared/renderer' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-account-about', | 9 | selector: 'my-account-about', |
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index e5c1f58b0..13b634a01 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts | |||
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Location } from '@angular/common' | 3 | import { Location } from '@angular/common' |
4 | import { immutableAssign } from '@app/shared/misc/utils' | 4 | import { immutableAssign } from '@app/shared/misc/utils' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { ConfirmService } from '../../core/confirm' | 6 | import { ConfirmService } from '../../core/confirm' |
8 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
@@ -13,6 +12,7 @@ import { tap } from 'rxjs/operators' | |||
13 | import { I18n } from '@ngx-translate/i18n-polyfill' | 12 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { Subscription } from 'rxjs' | 13 | import { Subscription } from 'rxjs' |
15 | import { ScreenService } from '@app/shared/misc/screen.service' | 14 | import { ScreenService } from '@app/shared/misc/screen.service' |
15 | import { Notifier } from '@app/core' | ||
16 | 16 | ||
17 | @Component({ | 17 | @Component({ |
18 | selector: 'my-account-videos', | 18 | selector: 'my-account-videos', |
@@ -35,7 +35,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
35 | protected router: Router, | 35 | protected router: Router, |
36 | protected route: ActivatedRoute, | 36 | protected route: ActivatedRoute, |
37 | protected authService: AuthService, | 37 | protected authService: AuthService, |
38 | protected notificationsService: NotificationsService, | 38 | protected notifier: Notifier, |
39 | protected confirmService: ConfirmService, | 39 | protected confirmService: ConfirmService, |
40 | protected location: Location, | 40 | protected location: Location, |
41 | protected screenService: ScreenService, | 41 | protected screenService: ScreenService, |
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index e19927d6b..e8339b78b 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts | |||
@@ -5,10 +5,9 @@ import { Account } from '@app/shared/account/account.model' | |||
5 | import { RestExtractor, UserService } from '@app/shared' | 5 | import { RestExtractor, UserService } from '@app/shared' |
6 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' | 6 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' |
7 | import { Subscription } from 'rxjs' | 7 | import { Subscription } from 'rxjs' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { AuthService, Notifier, RedirectService } from '@app/core' |
9 | import { User, UserRight } from '../../../../shared' | 9 | import { User, UserRight } from '../../../../shared' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { AuthService, RedirectService } from '@app/core' | ||
12 | 11 | ||
13 | @Component({ | 12 | @Component({ |
14 | templateUrl: './accounts.component.html', | 13 | templateUrl: './accounts.component.html', |
@@ -24,11 +23,10 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
24 | private route: ActivatedRoute, | 23 | private route: ActivatedRoute, |
25 | private userService: UserService, | 24 | private userService: UserService, |
26 | private accountService: AccountService, | 25 | private accountService: AccountService, |
27 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
28 | private restExtractor: RestExtractor, | 27 | private restExtractor: RestExtractor, |
29 | private redirectService: RedirectService, | 28 | private redirectService: RedirectService, |
30 | private authService: AuthService, | 29 | private authService: AuthService |
31 | private i18n: I18n | ||
32 | ) {} | 30 | ) {} |
33 | 31 | ||
34 | ngOnInit () { | 32 | ngOnInit () { |
@@ -43,7 +41,7 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
43 | .subscribe( | 41 | .subscribe( |
44 | account => this.account = account, | 42 | account => this.account = account, |
45 | 43 | ||
46 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 44 | err => this.notifier.error(err.message) |
47 | ) | 45 | ) |
48 | } | 46 | } |
49 | 47 | ||
@@ -69,7 +67,7 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
69 | .subscribe( | 67 | .subscribe( |
70 | user => this.user = user, | 68 | user => this.user = user, |
71 | 69 | ||
72 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 70 | err => this.notifier.error(err.message) |
73 | ) | 71 | ) |
74 | } | 72 | } |
75 | } | 73 | } |
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 fd4d3d9c9..52eb00d93 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 | |||
@@ -7,169 +7,169 @@ | |||
7 | 7 | ||
8 | <div i18n class="inner-form-title">Instance</div> | 8 | <div i18n class="inner-form-title">Instance</div> |
9 | 9 | ||
10 | <div class="form-group"> | 10 | <ng-container formGroupName="instance"> |
11 | <label i18n for="instanceName">Name</label> | 11 | <div class="form-group"> |
12 | <input | 12 | <label i18n for="instanceName">Name</label> |
13 | type="text" id="instanceName" | 13 | <input |
14 | formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }" | 14 | type="text" id="instanceName" |
15 | > | 15 | formControlName="name" [ngClass]="{ 'input-error': formErrors.instance.name }" |
16 | <div *ngIf="formErrors.instanceName" class="form-error"> | 16 | > |
17 | {{ formErrors.instanceName }} | 17 | <div *ngIf="formErrors.instance.name" class="form-error">{{ formErrors.instance.name }}</div> |
18 | </div> | 18 | </div> |
19 | </div> | ||
20 | 19 | ||
21 | <div class="form-group"> | 20 | <div class="form-group"> |
22 | <label i18n for="instanceShortDescription">Short description</label> | 21 | <label i18n for="instanceShortDescription">Short description</label> |
23 | <textarea | 22 | <textarea |
24 | id="instanceShortDescription" formControlName="instanceShortDescription" | 23 | id="instanceShortDescription" formControlName="shortDescription" |
25 | [ngClass]="{ 'input-error': formErrors['instanceShortDescription'] }" | 24 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" |
26 | ></textarea> | 25 | ></textarea> |
27 | <div *ngIf="formErrors.instanceShortDescription" class="form-error"> | 26 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> |
28 | {{ formErrors.instanceShortDescription }} | ||
29 | </div> | 27 | </div> |
30 | </div> | ||
31 | 28 | ||
32 | <div class="form-group"> | 29 | <div class="form-group"> |
33 | <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help> | 30 | <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help> |
34 | <my-markdown-textarea | 31 | <my-markdown-textarea |
35 | id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true" | 32 | id="instanceDescription" formControlName="description" textareaWidth="500px" [previewColumn]="true" |
36 | [classes]="{ 'input-error': formErrors['instanceDescription'] }" | 33 | [classes]="{ 'input-error': formErrors['instance.description'] }" |
37 | ></my-markdown-textarea> | 34 | ></my-markdown-textarea> |
38 | <div *ngIf="formErrors.instanceDescription" class="form-error"> | 35 | <div *ngIf="formErrors.instance.description" class="form-error">{{ formErrors.instance.description }}</div> |
39 | {{ formErrors.instanceDescription }} | ||
40 | </div> | 36 | </div> |
41 | </div> | ||
42 | 37 | ||
43 | <div class="form-group"> | 38 | <div class="form-group"> |
44 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 39 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> |
45 | <my-markdown-textarea | 40 | <my-markdown-textarea |
46 | id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true" | 41 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" |
47 | [ngClass]="{ 'input-error': formErrors['instanceTerms'] }" | 42 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" |
48 | ></my-markdown-textarea> | 43 | ></my-markdown-textarea> |
49 | <div *ngIf="formErrors.instanceTerms" class="form-error"> | 44 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> |
50 | {{ formErrors.instanceTerms }} | ||
51 | </div> | 45 | </div> |
52 | </div> | ||
53 | 46 | ||
54 | <div class="form-group"> | 47 | <div class="form-group"> |
55 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | 48 | <label i18n for="instanceDefaultClientRoute">Default client route</label> |
56 | <div class="peertube-select-container"> | 49 | <div class="peertube-select-container"> |
57 | <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute"> | 50 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> |
58 | <option i18n value="/videos/overview">Videos Overview</option> | 51 | <option i18n value="/videos/overview">Videos Overview</option> |
59 | <option i18n value="/videos/trending">Videos Trending</option> | 52 | <option i18n value="/videos/trending">Videos Trending</option> |
60 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | 53 | <option i18n value="/videos/recently-added">Videos Recently Added</option> |
61 | <option i18n value="/videos/local">Local videos</option> | 54 | <option i18n value="/videos/local">Local videos</option> |
62 | </select> | 55 | </select> |
56 | </div> | ||
57 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | ||
63 | </div> | 58 | </div> |
64 | <div *ngIf="formErrors.instanceDefaultClientRoute" class="form-error"> | 59 | |
65 | {{ formErrors.instanceDefaultClientRoute }} | 60 | <div class="form-group"> |
61 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | ||
62 | <my-help | ||
63 | helpType="custom" i18n-customHtml | ||
64 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | ||
65 | ></my-help> | ||
66 | |||
67 | <div class="peertube-select-container"> | ||
68 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> | ||
69 | <option i18n value="do_not_list">Do not list</option> | ||
70 | <option i18n value="blur">Blur thumbnails</option> | ||
71 | <option i18n value="display">Display</option> | ||
72 | </select> | ||
73 | </div> | ||
74 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> | ||
66 | </div> | 75 | </div> |
67 | </div> | 76 | </ng-container> |
68 | 77 | ||
69 | <div class="form-group"> | 78 | <div i18n class="inner-form-title">Signup</div> |
70 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | ||
71 | <my-help | ||
72 | helpType="custom" i18n-customHtml | ||
73 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | ||
74 | ></my-help> | ||
75 | 79 | ||
76 | <div class="peertube-select-container"> | 80 | <ng-container formGroupName="signup"> |
77 | <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy"> | 81 | <div class="form-group"> |
78 | <option i18n value="do_not_list">Do not list</option> | 82 | <my-peertube-checkbox |
79 | <option i18n value="blur">Blur thumbnails</option> | 83 | inputName="signupEnabled" formControlName="enabled" |
80 | <option i18n value="display">Display</option> | 84 | i18n-labelText labelText="Signup enabled" |
81 | </select> | 85 | ></my-peertube-checkbox> |
82 | </div> | 86 | </div> |
83 | <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error"> | 87 | |
84 | {{ formErrors.instanceDefaultNSFWPolicy }} | 88 | <div class="form-group"> |
89 | <my-peertube-checkbox *ngIf="isSignupEnabled()" | ||
90 | inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification" | ||
91 | i18n-labelText labelText="Signup requires email verification" | ||
92 | ></my-peertube-checkbox> | ||
85 | </div> | 93 | </div> |
86 | </div> | ||
87 | 94 | ||
88 | <div i18n class="inner-form-title">Signup</div> | 95 | <div *ngIf="isSignupEnabled()" class="form-group"> |
96 | <label i18n for="signupLimit">Signup limit</label> | ||
97 | <input | ||
98 | type="text" id="signupLimit" | ||
99 | formControlName="limit" [ngClass]="{ 'input-error': formErrors['signup.limit'] }" | ||
100 | > | ||
101 | <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> | ||
102 | </div> | ||
103 | </ng-container> | ||
89 | 104 | ||
90 | <div class="form-group"> | 105 | <div i18n class="inner-form-title">Users</div> |
91 | <my-peertube-checkbox | ||
92 | inputName="signupEnabled" formControlName="signupEnabled" | ||
93 | i18n-labelText labelText="Signup enabled" | ||
94 | ></my-peertube-checkbox> | ||
95 | </div> | ||
96 | 106 | ||
97 | <div class="form-group"> | 107 | <ng-container formGroupName="user"> |
98 | <my-peertube-checkbox *ngIf="isSignupEnabled()" | 108 | <div class="form-group"> |
99 | inputName="signupRequiresEmailVerification" formControlName="signupRequiresEmailVerification" | 109 | <label i18n for="userVideoQuota">User default video quota</label> |
100 | i18n-labelText labelText="Signup requires email verification" | 110 | <div class="peertube-select-container"> |
101 | ></my-peertube-checkbox> | 111 | <select id="userVideoQuota" formControlName="videoQuota"> |
102 | </div> | 112 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> |
113 | {{ videoQuotaOption.label }} | ||
114 | </option> | ||
115 | </select> | ||
116 | </div> | ||
117 | <div *ngIf="formErrors.user.videoQuota" class="form-error">{{ formErrors.user.videoQuota }}</div> | ||
118 | </div> | ||
103 | 119 | ||
104 | <div *ngIf="isSignupEnabled()" class="form-group"> | 120 | <div class="form-group"> |
105 | <label i18n for="signupLimit">Signup limit</label> | 121 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> |
106 | <input | 122 | <div class="peertube-select-container"> |
107 | type="text" id="signupLimit" | 123 | <select id="userVideoQuotaDaily" formControlName="videoQuotaDaily"> |
108 | formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }" | 124 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> |
109 | > | 125 | {{ videoQuotaDailyOption.label }} |
110 | <div *ngIf="formErrors.signupLimit" class="form-error"> | 126 | </option> |
111 | {{ formErrors.signupLimit }} | 127 | </select> |
128 | </div> | ||
129 | <div *ngIf="formErrors.user.videoQuotaDaily" class="form-error">{{ formErrors.user.videoQuotaDaily }}</div> | ||
112 | </div> | 130 | </div> |
113 | </div> | 131 | </ng-container> |
114 | 132 | ||
115 | <div i18n class="inner-form-title">Import</div> | 133 | <div i18n class="inner-form-title">Import</div> |
116 | 134 | ||
117 | <div class="form-group"> | 135 | <ng-container formGroupName="import"> |
118 | <my-peertube-checkbox | 136 | <ng-container formGroupName="videos"> |
119 | inputName="importVideosHttpEnabled" formControlName="importVideosHttpEnabled" | ||
120 | i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled" | ||
121 | ></my-peertube-checkbox> | ||
122 | </div> | ||
123 | 137 | ||
124 | <div class="form-group"> | 138 | <div class="form-group" formGroupName="http"> |
125 | <my-peertube-checkbox | 139 | <my-peertube-checkbox |
126 | inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled" | 140 | inputName="importVideosHttpEnabled" formControlName="enabled" |
127 | i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled" | 141 | i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled" |
128 | ></my-peertube-checkbox> | 142 | ></my-peertube-checkbox> |
129 | </div> | 143 | </div> |
144 | |||
145 | <div class="form-group" formGroupName="torrent"> | ||
146 | <my-peertube-checkbox | ||
147 | inputName="importVideosTorrentEnabled" formControlName="enabled" | ||
148 | i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled" | ||
149 | ></my-peertube-checkbox> | ||
150 | </div> | ||
151 | |||
152 | </ng-container> | ||
153 | </ng-container> | ||
130 | 154 | ||
131 | <div i18n class="inner-form-title">Administrator</div> | 155 | <div i18n class="inner-form-title">Administrator</div> |
132 | 156 | ||
133 | <div class="form-group"> | 157 | <div class="form-group" formGroupName="admin"> |
134 | <label i18n for="adminEmail">Admin email</label> | 158 | <label i18n for="adminEmail">Admin email</label> |
135 | <input | 159 | <input |
136 | type="text" id="adminEmail" | 160 | type="text" id="adminEmail" |
137 | formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }" | 161 | formControlName="email" [ngClass]="{ 'input-error': formErrors['admin.email'] }" |
138 | > | 162 | > |
139 | <div *ngIf="formErrors.adminEmail" class="form-error"> | 163 | <div *ngIf="formErrors.admin.email" class="form-error">{{ formErrors.admin.email }}</div> |
140 | {{ formErrors.adminEmail }} | ||
141 | </div> | ||
142 | </div> | 164 | </div> |
143 | 165 | ||
144 | <div i18n class="inner-form-title">Users</div> | 166 | <div class="form-group" formGroupName="contactForm"> |
145 | 167 | <my-peertube-checkbox | |
146 | <div class="form-group"> | 168 | inputName="enableContactForm" formControlName="enabled" |
147 | <label i18n for="userVideoQuota">User default video quota</label> | 169 | i18n-labelText labelText="Enable contact form" |
148 | <div class="peertube-select-container"> | 170 | ></my-peertube-checkbox> |
149 | <select id="userVideoQuota" formControlName="userVideoQuota"> | ||
150 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> | ||
151 | {{ videoQuotaOption.label }} | ||
152 | </option> | ||
153 | </select> | ||
154 | </div> | ||
155 | <div *ngIf="formErrors.userVideoQuota" class="form-error"> | ||
156 | {{ formErrors.userVideoQuota }} | ||
157 | </div> | ||
158 | </div> | 171 | </div> |
159 | 172 | ||
160 | <div class="form-group"> | ||
161 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> | ||
162 | <div class="peertube-select-container"> | ||
163 | <select id="userVideoQuotaDaily" formControlName="userVideoQuotaDaily"> | ||
164 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> | ||
165 | {{ videoQuotaDailyOption.label }} | ||
166 | </option> | ||
167 | </select> | ||
168 | </div> | ||
169 | <div *ngIf="formErrors.userVideoQuotaDaily" class="form-error"> | ||
170 | {{ formErrors.userVideoQuotaDaily }} | ||
171 | </div> | ||
172 | </div> | ||
173 | </ng-template> | 173 | </ng-template> |
174 | </ngb-tab> | 174 | </ngb-tab> |
175 | 175 | ||
@@ -177,30 +177,35 @@ | |||
177 | <ng-template ngbTabContent> | 177 | <ng-template ngbTabContent> |
178 | <div i18n class="inner-form-title">Twitter</div> | 178 | <div i18n class="inner-form-title">Twitter</div> |
179 | 179 | ||
180 | <div class="form-group"> | 180 | <ng-container formGroupName="services"> |
181 | <label i18n for="signupLimit">Your Twitter username</label> | 181 | <ng-container formGroupName="twitter"> |
182 | <my-help | 182 | |
183 | helpType="custom" i18n-customHtml | 183 | <div class="form-group"> |
184 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." | 184 | <label i18n for="signupLimit">Your Twitter username</label> |
185 | ></my-help> | 185 | <my-help |
186 | <input | 186 | helpType="custom" i18n-customHtml |
187 | type="text" id="servicesTwitterUsername" | 187 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." |
188 | formControlName="servicesTwitterUsername" [ngClass]="{ 'input-error': formErrors['servicesTwitterUsername'] }" | 188 | ></my-help> |
189 | > | 189 | <input |
190 | <div *ngIf="formErrors.servicesTwitterUsername" class="form-error"> | 190 | type="text" id="servicesTwitterUsername" |
191 | {{ formErrors.servicesTwitterUsername }} | 191 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" |
192 | </div> | 192 | > |
193 | </div> | 193 | <div *ngIf="formErrors.services.twitter.username" class="form-error">{{ formErrors.services.twitter.username }}</div> |
194 | </div> | ||
195 | |||
196 | <div class="form-group"> | ||
197 | <my-peertube-checkbox | ||
198 | inputName="servicesTwitterWhitelisted" formControlName="whitelisted" | ||
199 | i18n-labelText labelText="Instance whitelisted by Twitter" | ||
200 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
201 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
202 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | ||
203 | ></my-peertube-checkbox> | ||
204 | </div> | ||
205 | |||
206 | </ng-container> | ||
207 | </ng-container> | ||
194 | 208 | ||
195 | <div class="form-group"> | ||
196 | <my-peertube-checkbox | ||
197 | inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted" | ||
198 | i18n-labelText labelText="Instance whitelisted by Twitter" | ||
199 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
200 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
201 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | ||
202 | ></my-peertube-checkbox> | ||
203 | </div> | ||
204 | </ng-template> | 209 | </ng-template> |
205 | </ngb-tab> | 210 | </ngb-tab> |
206 | 211 | ||
@@ -209,37 +214,48 @@ | |||
209 | 214 | ||
210 | <div i18n class="inner-form-title">Transcoding</div> | 215 | <div i18n class="inner-form-title">Transcoding</div> |
211 | 216 | ||
212 | <div class="form-group"> | 217 | <ng-container formGroupName="transcoding"> |
213 | <my-peertube-checkbox | 218 | <div class="form-group"> |
214 | inputName="transcodingEnabled" formControlName="transcodingEnabled" | 219 | <my-peertube-checkbox |
215 | i18n-labelText labelText="Transcoding enabled" | 220 | inputName="transcodingEnabled" formControlName="enabled" |
216 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | 221 | i18n-labelText labelText="Transcoding enabled" |
217 | ></my-peertube-checkbox> | 222 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" |
218 | </div> | 223 | ></my-peertube-checkbox> |
224 | </div> | ||
219 | 225 | ||
220 | <ng-template [ngIf]="isTranscodingEnabled()"> | 226 | <ng-container *ngIf="isTranscodingEnabled()"> |
221 | 227 | ||
222 | <div class="form-group"> | 228 | <div class="form-group"> |
223 | <label i18n for="transcodingThreads">Transcoding threads</label> | 229 | <my-peertube-checkbox |
224 | <div class="peertube-select-container"> | 230 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" |
225 | <select id="transcodingThreads" formControlName="transcodingThreads"> | 231 | i18n-labelText labelText="Allow additional extensions" |
226 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | 232 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" |
227 | {{ transcodingThreadOption.label }} | 233 | ></my-peertube-checkbox> |
228 | </option> | ||
229 | </select> | ||
230 | </div> | 234 | </div> |
231 | <div *ngIf="formErrors.transcodingThreads" class="form-error"> | 235 | |
232 | {{ formErrors.transcodingThreads }} | 236 | <div class="form-group"> |
237 | <label i18n for="transcodingThreads">Transcoding threads</label> | ||
238 | <div class="peertube-select-container"> | ||
239 | <select id="transcodingThreads" formControlName="threads"> | ||
240 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | ||
241 | {{ transcodingThreadOption.label }} | ||
242 | </option> | ||
243 | </select> | ||
244 | </div> | ||
245 | <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div> | ||
233 | </div> | 246 | </div> |
234 | </div> | ||
235 | 247 | ||
236 | <div class="form-group" *ngFor="let resolution of resolutions"> | 248 | <ng-container formGroupName="resolutions"> |
237 | <my-peertube-checkbox | 249 | <div class="form-group" *ngFor="let resolution of resolutions"> |
238 | [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)" | 250 | <my-peertube-checkbox |
239 | i18n-labelText labelText="Resolution {{resolution}} enabled" | 251 | [inputName]="getResolutionKey(resolution)" [formControlName]="resolution" |
240 | ></my-peertube-checkbox> | 252 | i18n-labelText labelText="Resolution {{resolution}} enabled" |
241 | </div> | 253 | ></my-peertube-checkbox> |
242 | </ng-template> | 254 | </div> |
255 | </ng-container> | ||
256 | |||
257 | </ng-container> | ||
258 | </ng-container> | ||
243 | 259 | ||
244 | <div i18n class="inner-form-title"> | 260 | <div i18n class="inner-form-title"> |
245 | Cache | 261 | Cache |
@@ -250,74 +266,73 @@ | |||
250 | ></my-help> | 266 | ></my-help> |
251 | </div> | 267 | </div> |
252 | 268 | ||
253 | <div class="form-group"> | 269 | <ng-container formGroupName="cache"> |
254 | <label i18n for="cachePreviewsSize">Previews cache size</label> | 270 | <div class="form-group" formGroupName="previews"> |
255 | <input | 271 | <label i18n for="cachePreviewsSize">Previews cache size</label> |
256 | type="text" id="cachePreviewsSize" | 272 | <input |
257 | formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }" | 273 | type="text" id="cachePreviewsSize" |
258 | > | 274 | formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.previews.size'] }" |
259 | <div *ngIf="formErrors.cachePreviewsSize" class="form-error"> | 275 | > |
260 | {{ formErrors.cachePreviewsSize }} | 276 | <div *ngIf="formErrors.cache.previews.size" class="form-error">{{ formErrors.cache.previews.size }}</div> |
261 | </div> | 277 | </div> |
262 | </div> | ||
263 | 278 | ||
264 | <div class="form-group"> | 279 | <div class="form-group" formGroupName="captions"> |
265 | <label i18n for="cachePreviewsSize">Video captions cache size</label> | 280 | <label i18n for="cacheCaptionsSize">Video captions cache size</label> |
266 | <input | 281 | <input |
267 | type="text" id="cacheCaptionsSize" | 282 | type="text" id="cacheCaptionsSize" |
268 | formControlName="cacheCaptionsSize" [ngClass]="{ 'input-error': formErrors['cacheCaptionsSize'] }" | 283 | formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.captions.size'] }" |
269 | > | 284 | > |
270 | <div *ngIf="formErrors.cacheCaptionsSize" class="form-error"> | 285 | <div *ngIf="formErrors.cache.captions.size" class="form-error">{{ formErrors.cache.captions.size }}</div> |
271 | {{ formErrors.cacheCaptionsSize }} | ||
272 | </div> | 286 | </div> |
273 | </div> | 287 | </ng-container> |
274 | 288 | ||
275 | <div i18n class="inner-form-title">Customizations</div> | 289 | <div i18n class="inner-form-title">Customizations</div> |
276 | 290 | ||
277 | <div class="form-group"> | 291 | <ng-container formGroupName="instance"> |
278 | <label i18n for="customizationJavascript">JavaScript</label> | 292 | <ng-container formGroupName="customizations"> |
279 | <my-help | 293 | <div class="form-group"> |
280 | helpType="custom" i18n-customHtml | 294 | <label i18n for="customizationJavascript">JavaScript</label> |
281 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" | 295 | <my-help |
282 | ></my-help> | 296 | helpType="custom" i18n-customHtml |
283 | <textarea | 297 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" |
284 | id="customizationJavascript" formControlName="customizationJavascript" | 298 | ></my-help> |
285 | [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }" | 299 | <textarea |
286 | ></textarea> | 300 | id="customizationJavascript" formControlName="javascript" |
287 | <div *ngIf="formErrors.customizationJavascript" class="form-error"> | 301 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" |
288 | {{ formErrors.customizationJavascript }} | 302 | ></textarea> |
289 | </div> | 303 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> |
290 | </div> | 304 | </div> |
305 | |||
306 | <div class="form-group"> | ||
307 | <label for="customizationCSS">CSS</label> | ||
308 | <my-help | ||
309 | helpType="custom" | ||
310 | i18n-customHtml | ||
311 | customHtml=" | ||
312 | Write directly CSS code. Example:<br /> | ||
313 | <pre> | ||
314 | body {{ '{' }} | ||
315 | background-color: red; | ||
316 | {{ '}' }} | ||
317 | </pre> | ||
318 | |||
319 | Prepend with <em>#custom-css</em> to override styles. Example: | ||
320 | <pre> | ||
321 | #custom-css .logged-in-email {{ '{' }} | ||
322 | color: red; | ||
323 | {{ '}' }} | ||
324 | </pre> | ||
325 | " | ||
326 | ></my-help> | ||
327 | <textarea | ||
328 | id="customizationCSS" formControlName="css" | ||
329 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" | ||
330 | ></textarea> | ||
331 | <div *ngIf="formErrors.instance.customizations.css" class="form-error">{{ formErrors.instance.customizations.css }}</div> | ||
332 | </div> | ||
333 | </ng-container> | ||
334 | </ng-container> | ||
291 | 335 | ||
292 | <div class="form-group"> | ||
293 | <label for="customizationCSS">CSS</label> | ||
294 | <my-help | ||
295 | helpType="custom" | ||
296 | i18n-customHtml | ||
297 | customHtml=" | ||
298 | Write directly CSS code. Example:<br /> | ||
299 | <pre> | ||
300 | body {{ '{' }} | ||
301 | background-color: red; | ||
302 | {{ '}' }} | ||
303 | </pre> | ||
304 | |||
305 | Prepend with <em>#custom-css</em> to override styles. Example: | ||
306 | <pre> | ||
307 | #custom-css .logged-in-email {{ '{' }} | ||
308 | color: red; | ||
309 | {{ '}' }} | ||
310 | </pre> | ||
311 | " | ||
312 | ></my-help> | ||
313 | <textarea | ||
314 | id="customizationCSS" formControlName="customizationCSS" | ||
315 | [ngClass]="{ 'input-error': formErrors['customizationCSS'] }" | ||
316 | ></textarea> | ||
317 | <div *ngIf="formErrors.customizationCSS" class="form-error"> | ||
318 | {{ formErrors.customizationCSS }} | ||
319 | </div> | ||
320 | </div> | ||
321 | </ng-template> | 336 | </ng-template> |
322 | </ngb-tab> | 337 | </ngb-tab> |
323 | </ngb-tabset> | 338 | </ngb-tabset> |
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 f48b6fc1a..654a076b0 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 | |||
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core' | |||
2 | import { ConfigService } from '@app/+admin/config/shared/config.service' | 2 | import { ConfigService } from '@app/+admin/config/shared/config.service' |
3 | import { ServerService } from '@app/core/server/server.service' | 3 | import { ServerService } from '@app/core/server/server.service' |
4 | import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared' | 4 | import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' | 6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
@@ -18,14 +18,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
18 | resolutions: string[] = [] | 18 | resolutions: string[] = [] |
19 | transcodingThreadOptions: { label: string, value: number }[] = [] | 19 | transcodingThreadOptions: { label: string, value: number }[] = [] |
20 | 20 | ||
21 | private oldCustomJavascript: string | ||
22 | private oldCustomCSS: string | ||
23 | |||
24 | constructor ( | 21 | constructor ( |
25 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
26 | private customConfigValidatorsService: CustomConfigValidatorsService, | 23 | private customConfigValidatorsService: CustomConfigValidatorsService, |
27 | private userValidatorsService: UserValidatorsService, | 24 | private userValidatorsService: UserValidatorsService, |
28 | private notificationsService: NotificationsService, | 25 | private notifier: Notifier, |
29 | private configService: ConfigService, | 26 | private configService: ConfigService, |
30 | private serverService: ServerService, | 27 | private serverService: ServerService, |
31 | private i18n: I18n | 28 | private i18n: I18n |
@@ -58,40 +55,78 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
58 | } | 55 | } |
59 | 56 | ||
60 | getResolutionKey (resolution: string) { | 57 | getResolutionKey (resolution: string) { |
61 | return 'transcodingResolution' + resolution | 58 | return 'transcoding.resolutions.' + resolution |
62 | } | 59 | } |
63 | 60 | ||
64 | ngOnInit () { | 61 | ngOnInit () { |
65 | const formGroupData: { [key: string]: any } = { | 62 | const formGroupData: { [key in keyof CustomConfig ]: any } = { |
66 | instanceName: this.customConfigValidatorsService.INSTANCE_NAME, | 63 | instance: { |
67 | instanceShortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 64 | name: this.customConfigValidatorsService.INSTANCE_NAME, |
68 | instanceDescription: null, | 65 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, |
69 | instanceTerms: null, | 66 | description: null, |
70 | instanceDefaultClientRoute: null, | 67 | terms: null, |
71 | instanceDefaultNSFWPolicy: null, | 68 | defaultClientRoute: null, |
72 | servicesTwitterUsername: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, | 69 | defaultNSFWPolicy: null, |
73 | servicesTwitterWhitelisted: null, | 70 | customizations: { |
74 | cachePreviewsSize: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE, | 71 | javascript: null, |
75 | cacheCaptionsSize: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE, | 72 | css: null |
76 | signupEnabled: null, | 73 | } |
77 | signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT, | 74 | }, |
78 | signupRequiresEmailVerification: null, | 75 | services: { |
79 | importVideosHttpEnabled: null, | 76 | twitter: { |
80 | importVideosTorrentEnabled: null, | 77 | username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, |
81 | adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, | 78 | whitelisted: null |
82 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 79 | } |
83 | userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | 80 | }, |
84 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, | 81 | cache: { |
85 | transcodingEnabled: null, | 82 | previews: { |
86 | customizationJavascript: null, | 83 | size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE |
87 | customizationCSS: null | 84 | }, |
85 | captions: { | ||
86 | size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE | ||
87 | } | ||
88 | }, | ||
89 | signup: { | ||
90 | enabled: null, | ||
91 | limit: this.customConfigValidatorsService.SIGNUP_LIMIT, | ||
92 | requiresEmailVerification: null | ||
93 | }, | ||
94 | import: { | ||
95 | videos: { | ||
96 | http: { | ||
97 | enabled: null | ||
98 | }, | ||
99 | torrent: { | ||
100 | enabled: null | ||
101 | } | ||
102 | } | ||
103 | }, | ||
104 | admin: { | ||
105 | email: this.customConfigValidatorsService.ADMIN_EMAIL | ||
106 | }, | ||
107 | contactForm: { | ||
108 | enabled: null | ||
109 | }, | ||
110 | user: { | ||
111 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | ||
112 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY | ||
113 | }, | ||
114 | transcoding: { | ||
115 | enabled: null, | ||
116 | threads: this.customConfigValidatorsService.TRANSCODING_THREADS, | ||
117 | allowAdditionalExtensions: null, | ||
118 | resolutions: {} | ||
119 | } | ||
88 | } | 120 | } |
89 | 121 | ||
90 | const defaultValues: BuildFormDefaultValues = {} | 122 | const defaultValues = { |
123 | transcoding: { | ||
124 | resolutions: {} | ||
125 | } | ||
126 | } | ||
91 | for (const resolution of this.resolutions) { | 127 | for (const resolution of this.resolutions) { |
92 | const key = this.getResolutionKey(resolution) | 128 | defaultValues.transcoding.resolutions[resolution] = 'false' |
93 | defaultValues[key] = 'false' | 129 | formGroupData.transcoding.resolutions[resolution] = null |
94 | formGroupData[key] = null | ||
95 | } | 130 | } |
96 | 131 | ||
97 | this.buildForm(formGroupData) | 132 | this.buildForm(formGroupData) |
@@ -101,90 +136,25 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
101 | res => { | 136 | res => { |
102 | this.customConfig = res | 137 | this.customConfig = res |
103 | 138 | ||
104 | this.oldCustomCSS = this.customConfig.instance.customizations.css | ||
105 | this.oldCustomJavascript = this.customConfig.instance.customizations.javascript | ||
106 | |||
107 | this.updateForm() | 139 | this.updateForm() |
108 | // Force form validation | 140 | // Force form validation |
109 | this.forceCheck() | 141 | this.forceCheck() |
110 | }, | 142 | }, |
111 | 143 | ||
112 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 144 | err => this.notifier.error(err.message) |
113 | ) | 145 | ) |
114 | } | 146 | } |
115 | 147 | ||
116 | isTranscodingEnabled () { | 148 | isTranscodingEnabled () { |
117 | return this.form.value['transcodingEnabled'] === true | 149 | return this.form.value['transcoding']['enabled'] === true |
118 | } | 150 | } |
119 | 151 | ||
120 | isSignupEnabled () { | 152 | isSignupEnabled () { |
121 | return this.form.value['signupEnabled'] === true | 153 | return this.form.value['signup']['enabled'] === true |
122 | } | 154 | } |
123 | 155 | ||
124 | async formValidated () { | 156 | async formValidated () { |
125 | const data: CustomConfig = { | 157 | this.configService.updateCustomConfig(this.form.value) |
126 | instance: { | ||
127 | name: this.form.value['instanceName'], | ||
128 | shortDescription: this.form.value['instanceShortDescription'], | ||
129 | description: this.form.value['instanceDescription'], | ||
130 | terms: this.form.value['instanceTerms'], | ||
131 | defaultClientRoute: this.form.value['instanceDefaultClientRoute'], | ||
132 | defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'], | ||
133 | customizations: { | ||
134 | javascript: this.form.value['customizationJavascript'], | ||
135 | css: this.form.value['customizationCSS'] | ||
136 | } | ||
137 | }, | ||
138 | services: { | ||
139 | twitter: { | ||
140 | username: this.form.value['servicesTwitterUsername'], | ||
141 | whitelisted: this.form.value['servicesTwitterWhitelisted'] | ||
142 | } | ||
143 | }, | ||
144 | cache: { | ||
145 | previews: { | ||
146 | size: this.form.value['cachePreviewsSize'] | ||
147 | }, | ||
148 | captions: { | ||
149 | size: this.form.value['cacheCaptionsSize'] | ||
150 | } | ||
151 | }, | ||
152 | signup: { | ||
153 | enabled: this.form.value['signupEnabled'], | ||
154 | limit: this.form.value['signupLimit'], | ||
155 | requiresEmailVerification: this.form.value['signupRequiresEmailVerification'] | ||
156 | }, | ||
157 | admin: { | ||
158 | email: this.form.value['adminEmail'] | ||
159 | }, | ||
160 | user: { | ||
161 | videoQuota: this.form.value['userVideoQuota'], | ||
162 | videoQuotaDaily: this.form.value['userVideoQuotaDaily'] | ||
163 | }, | ||
164 | transcoding: { | ||
165 | enabled: this.form.value['transcodingEnabled'], | ||
166 | threads: this.form.value['transcodingThreads'], | ||
167 | resolutions: { | ||
168 | '240p': this.form.value[this.getResolutionKey('240p')], | ||
169 | '360p': this.form.value[this.getResolutionKey('360p')], | ||
170 | '480p': this.form.value[this.getResolutionKey('480p')], | ||
171 | '720p': this.form.value[this.getResolutionKey('720p')], | ||
172 | '1080p': this.form.value[this.getResolutionKey('1080p')] | ||
173 | } | ||
174 | }, | ||
175 | import: { | ||
176 | videos: { | ||
177 | http: { | ||
178 | enabled: this.form.value['importVideosHttpEnabled'] | ||
179 | }, | ||
180 | torrent: { | ||
181 | enabled: this.form.value['importVideosTorrentEnabled'] | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | |||
187 | this.configService.updateCustomConfig(data) | ||
188 | .subscribe( | 158 | .subscribe( |
189 | res => { | 159 | res => { |
190 | this.customConfig = res | 160 | this.customConfig = res |
@@ -194,45 +164,15 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
194 | 164 | ||
195 | this.updateForm() | 165 | this.updateForm() |
196 | 166 | ||
197 | this.notificationsService.success(this.i18n('Success'), this.i18n('Configuration updated.')) | 167 | this.notifier.success(this.i18n('Configuration updated.')) |
198 | }, | 168 | }, |
199 | 169 | ||
200 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 170 | err => this.notifier.error(err.message) |
201 | ) | 171 | ) |
202 | } | 172 | } |
203 | 173 | ||
204 | private updateForm () { | 174 | private updateForm () { |
205 | const data: { [key: string]: any } = { | 175 | this.form.patchValue(this.customConfig) |
206 | instanceName: this.customConfig.instance.name, | ||
207 | instanceShortDescription: this.customConfig.instance.shortDescription, | ||
208 | instanceDescription: this.customConfig.instance.description, | ||
209 | instanceTerms: this.customConfig.instance.terms, | ||
210 | instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute, | ||
211 | instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy, | ||
212 | servicesTwitterUsername: this.customConfig.services.twitter.username, | ||
213 | servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted, | ||
214 | cachePreviewsSize: this.customConfig.cache.previews.size, | ||
215 | cacheCaptionsSize: this.customConfig.cache.captions.size, | ||
216 | signupEnabled: this.customConfig.signup.enabled, | ||
217 | signupLimit: this.customConfig.signup.limit, | ||
218 | signupRequiresEmailVerification: this.customConfig.signup.requiresEmailVerification, | ||
219 | adminEmail: this.customConfig.admin.email, | ||
220 | userVideoQuota: this.customConfig.user.videoQuota, | ||
221 | userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, | ||
222 | transcodingThreads: this.customConfig.transcoding.threads, | ||
223 | transcodingEnabled: this.customConfig.transcoding.enabled, | ||
224 | customizationJavascript: this.customConfig.instance.customizations.javascript, | ||
225 | customizationCSS: this.customConfig.instance.customizations.css, | ||
226 | importVideosHttpEnabled: this.customConfig.import.videos.http.enabled, | ||
227 | importVideosTorrentEnabled: this.customConfig.import.videos.torrent.enabled | ||
228 | } | ||
229 | |||
230 | for (const resolution of this.resolutions) { | ||
231 | const key = this.getResolutionKey(resolution) | ||
232 | data[key] = this.customConfig.transcoding.resolutions[resolution] | ||
233 | } | ||
234 | |||
235 | this.form.patchValue(data) | ||
236 | } | 176 | } |
237 | 177 | ||
238 | } | 178 | } |
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts index 4a25b7ff3..9a8848bfb 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | 2 | ||
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/primeng' | 4 | import { SortMeta } from 'primeng/primeng' |
5 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' | 5 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' |
6 | import { RestPagination, RestTable } from '../../../shared' | 6 | import { RestPagination, RestTable } from '../../../shared' |
@@ -20,7 +20,7 @@ export class FollowersListComponent extends RestTable implements OnInit { | |||
20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
21 | 21 | ||
22 | constructor ( | 22 | constructor ( |
23 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
24 | private followService: FollowService, | 24 | private followService: FollowService, |
25 | private i18n: I18n | 25 | private i18n: I18n |
26 | ) { | 26 | ) { |
@@ -32,14 +32,14 @@ export class FollowersListComponent extends RestTable implements OnInit { | |||
32 | } | 32 | } |
33 | 33 | ||
34 | protected loadData () { | 34 | protected loadData () { |
35 | this.followService.getFollowers(this.pagination, this.sort) | 35 | this.followService.getFollowers(this.pagination, this.sort, this.search) |
36 | .subscribe( | 36 | .subscribe( |
37 | resultList => { | 37 | resultList => { |
38 | this.followers = resultList.data | 38 | this.followers = resultList.data |
39 | this.totalRecords = resultList.total | 39 | this.totalRecords = resultList.total |
40 | }, | 40 | }, |
41 | 41 | ||
42 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 42 | err => this.notifier.error(err.message) |
43 | ) | 43 | ) |
44 | } | 44 | } |
45 | } | 45 | } |
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts index bd9cc022b..2bb249746 100644 --- a/client/src/app/+admin/follows/following-add/following-add.component.ts +++ b/client/src/app/+admin/follows/following-add/following-add.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { ConfirmService } from '../../../core' | 4 | import { ConfirmService } from '../../../core' |
5 | import { validateHost } from '../../../shared' | 5 | import { validateHost } from '../../../shared' |
6 | import { FollowService } from '../shared' | 6 | import { FollowService } from '../shared' |
@@ -18,7 +18,7 @@ export class FollowingAddComponent { | |||
18 | 18 | ||
19 | constructor ( | 19 | constructor ( |
20 | private router: Router, | 20 | private router: Router, |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private confirmService: ConfirmService, | 22 | private confirmService: ConfirmService, |
23 | private followService: FollowService, | 23 | private followService: FollowService, |
24 | private i18n: I18n | 24 | private i18n: I18n |
@@ -64,12 +64,12 @@ export class FollowingAddComponent { | |||
64 | 64 | ||
65 | this.followService.follow(hosts).subscribe( | 65 | this.followService.follow(hosts).subscribe( |
66 | () => { | 66 | () => { |
67 | this.notificationsService.success(this.i18n('Success'), this.i18n('Follow request(s) sent!')) | 67 | this.notifier.success(this.i18n('Follow request(s) sent!')) |
68 | 68 | ||
69 | setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) | 69 | setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) |
70 | }, | 70 | }, |
71 | 71 | ||
72 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 72 | err => this.notifier.error(err.message) |
73 | ) | 73 | ) |
74 | } | 74 | } |
75 | 75 | ||
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 9b7029f75..4517a721e 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { SortMeta } from 'primeng/primeng' | 3 | import { SortMeta } from 'primeng/primeng' |
4 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' | 4 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' |
5 | import { ConfirmService } from '../../../core/confirm/confirm.service' | 5 | import { ConfirmService } from '../../../core/confirm/confirm.service' |
@@ -20,7 +20,7 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
21 | 21 | ||
22 | constructor ( | 22 | constructor ( |
23 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
24 | private confirmService: ConfirmService, | 24 | private confirmService: ConfirmService, |
25 | private followService: FollowService, | 25 | private followService: FollowService, |
26 | private i18n: I18n | 26 | private i18n: I18n |
@@ -41,14 +41,11 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
41 | 41 | ||
42 | this.followService.unfollow(follow).subscribe( | 42 | this.followService.unfollow(follow).subscribe( |
43 | () => { | 43 | () => { |
44 | this.notificationsService.success( | 44 | this.notifier.success(this.i18n('You are not following {{host}} anymore.', { host: follow.following.host })) |
45 | this.i18n('Success'), | ||
46 | this.i18n('You are not following {{host}} anymore.', { host: follow.following.host }) | ||
47 | ) | ||
48 | this.loadData() | 45 | this.loadData() |
49 | }, | 46 | }, |
50 | 47 | ||
51 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 48 | err => this.notifier.error(err.message) |
52 | ) | 49 | ) |
53 | } | 50 | } |
54 | 51 | ||
@@ -60,7 +57,7 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
60 | this.totalRecords = resultList.total | 57 | this.totalRecords = resultList.total |
61 | }, | 58 | }, |
62 | 59 | ||
63 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 60 | err => this.notifier.error(err.message) |
64 | ) | 61 | ) |
65 | } | 62 | } |
66 | } | 63 | } |
diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts index 6d77a0eb4..fa1da26bf 100644 --- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts +++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' | 4 | import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' |
5 | 5 | ||
@@ -13,24 +13,21 @@ export class RedundancyCheckboxComponent { | |||
13 | @Input() host: string | 13 | @Input() host: string |
14 | 14 | ||
15 | constructor ( | 15 | constructor ( |
16 | private notificationsService: NotificationsService, | 16 | private notifier: Notifier, |
17 | private redundancyService: RedundancyService, | 17 | private redundancyService: RedundancyService, |
18 | private i18n: I18n | 18 | private i18n: I18n |
19 | ) { } | 19 | ) { } |
20 | 20 | ||
21 | updateRedundancyState () { | 21 | updateRedundancyState () { |
22 | this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed) | 22 | this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed) |
23 | .subscribe( | 23 | .subscribe( |
24 | () => { | 24 | () => { |
25 | const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled') | 25 | const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled') |
26 | 26 | ||
27 | this.notificationsService.success( | 27 | this.notifier.success(this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel })) |
28 | this.i18n('Success'), | 28 | }, |
29 | this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel }) | ||
30 | ) | ||
31 | }, | ||
32 | 29 | ||
33 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 30 | err => this.notifier.error(err.message) |
34 | ) | 31 | ) |
35 | } | 32 | } |
36 | } | 33 | } |
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts index 44778ab56..b265e1dd6 100644 --- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts +++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 2 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/primeng' | 4 | import { SortMeta } from 'primeng/primeng' |
5 | import { Job } from '../../../../../../shared/index' | 5 | import { Job } from '../../../../../../shared/index' |
6 | import { JobState } from '../../../../../../shared/models' | 6 | import { JobState } from '../../../../../../shared/models' |
@@ -25,7 +25,7 @@ export class JobsListComponent extends RestTable implements OnInit { | |||
25 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 25 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
26 | 26 | ||
27 | constructor ( | 27 | constructor ( |
28 | private notificationsService: NotificationsService, | 28 | private notifier: Notifier, |
29 | private jobsService: JobService, | 29 | private jobsService: JobService, |
30 | private i18n: I18n | 30 | private i18n: I18n |
31 | ) { | 31 | ) { |
@@ -53,7 +53,7 @@ export class JobsListComponent extends RestTable implements OnInit { | |||
53 | this.totalRecords = resultList.total | 53 | this.totalRecords = resultList.total |
54 | }, | 54 | }, |
55 | 55 | ||
56 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 56 | err => this.notifier.error(err.message) |
57 | ) | 57 | ) |
58 | } | 58 | } |
59 | 59 | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts index 3f243aee4..032bf745a 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RestPagination, RestTable } from '@app/shared' | 4 | import { RestPagination, RestTable } from '@app/shared' |
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | 5 | import { SortMeta } from 'primeng/components/common/sortmeta' |
6 | import { BlocklistService, AccountBlock } from '@app/shared/blocklist' | 6 | import { AccountBlock, BlocklistService } from '@app/shared/blocklist' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-instance-account-blocklist', | 9 | selector: 'my-instance-account-blocklist', |
@@ -18,7 +18,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn | |||
18 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 18 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
19 | 19 | ||
20 | constructor ( | 20 | constructor ( |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private blocklistService: BlocklistService, | 22 | private blocklistService: BlocklistService, |
23 | private i18n: I18n | 23 | private i18n: I18n |
24 | ) { | 24 | ) { |
@@ -35,8 +35,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn | |||
35 | this.blocklistService.unblockAccountByInstance(blockedAccount) | 35 | this.blocklistService.unblockAccountByInstance(blockedAccount) |
36 | .subscribe( | 36 | .subscribe( |
37 | () => { | 37 | () => { |
38 | this.notificationsService.success( | 38 | this.notifier.success( |
39 | this.i18n('Success'), | ||
40 | this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost }) | 39 | this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost }) |
41 | ) | 40 | ) |
42 | 41 | ||
@@ -53,7 +52,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn | |||
53 | this.totalRecords = resultList.total | 52 | this.totalRecords = resultList.total |
54 | }, | 53 | }, |
55 | 54 | ||
56 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 55 | err => this.notifier.error(err.message) |
57 | ) | 56 | ) |
58 | } | 57 | } |
59 | } | 58 | } |
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts index 130009dc7..db3dfcd1c 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RestPagination, RestTable } from '@app/shared' | 4 | import { RestPagination, RestTable } from '@app/shared' |
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | 5 | import { SortMeta } from 'primeng/components/common/sortmeta' |
@@ -19,7 +19,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni | |||
19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
20 | 20 | ||
21 | constructor ( | 21 | constructor ( |
22 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
23 | private blocklistService: BlocklistService, | 23 | private blocklistService: BlocklistService, |
24 | private i18n: I18n | 24 | private i18n: I18n |
25 | ) { | 25 | ) { |
@@ -36,10 +36,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni | |||
36 | this.blocklistService.unblockServerByInstance(host) | 36 | this.blocklistService.unblockServerByInstance(host) |
37 | .subscribe( | 37 | .subscribe( |
38 | () => { | 38 | () => { |
39 | this.notificationsService.success( | 39 | this.notifier.success(this.i18n('Instance {{host}} unmuted by your instance.', { host })) |
40 | this.i18n('Success'), | ||
41 | this.i18n('Instance {{host}} unmuted by your instance.', { host }) | ||
42 | ) | ||
43 | 40 | ||
44 | this.loadData() | 41 | this.loadData() |
45 | } | 42 | } |
@@ -54,7 +51,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni | |||
54 | this.totalRecords = resultList.total | 51 | this.totalRecords = resultList.total |
55 | }, | 52 | }, |
56 | 53 | ||
57 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 54 | err => this.notifier.error(err.message) |
58 | ) | 55 | ) |
59 | } | 56 | } |
60 | } | 57 | } |
diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index 02ccfc8ca..13b019c5b 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss | |||
@@ -10,6 +10,7 @@ | |||
10 | font-weight: $font-semibold; | 10 | font-weight: $font-semibold; |
11 | min-width: 200px; | 11 | min-width: 200px; |
12 | display: inline-block; | 12 | display: inline-block; |
13 | vertical-align: top; | ||
13 | } | 14 | } |
14 | 15 | ||
15 | .moderation-expanded-text { | 16 | .moderation-expanded-text { |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html index 3a8424f68..303a788d2 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html | |||
@@ -1,7 +1,8 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Moderation comment</h4> | 3 | <h4 i18n class="modal-title">Moderation comment</h4> |
4 | <span class="close" aria-hidden="true" (click)="hideModerationCommentModal()"></span> | 4 | |
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | 6 | </div> |
6 | 7 | ||
7 | <div class="modal-body"> | 8 | <div class="modal-body"> |
@@ -14,12 +15,12 @@ | |||
14 | </div> | 15 | </div> |
15 | </div> | 16 | </div> |
16 | 17 | ||
17 | <div i18n> | 18 | <div class="form-group" i18n> |
18 | This comment can only be seen by you or the other moderators. | 19 | This comment can only be seen by you or the other moderators. |
19 | </div> | 20 | </div> |
20 | 21 | ||
21 | <div class="form-group inputs"> | 22 | <div class="form-group inputs"> |
22 | <span i18n class="action-button action-button-cancel" (click)="hideModerationCommentModal()">Cancel</span> | 23 | <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span> |
23 | 24 | ||
24 | <input | 25 | <input |
25 | type="submit" i18n-value value="Update this comment" class="action-button-submit" | 26 | type="submit" i18n-value value="Update this comment" class="action-button-submit" |
@@ -29,4 +30,4 @@ | |||
29 | </form> | 30 | </form> |
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | </ng-template> \ No newline at end of file | 33 | </ng-template> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts index 34ab384d1..f915978ee 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared' | 3 | import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
@@ -22,7 +22,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
22 | constructor ( | 22 | constructor ( |
23 | protected formValidatorService: FormValidatorService, | 23 | protected formValidatorService: FormValidatorService, |
24 | private modalService: NgbModal, | 24 | private modalService: NgbModal, |
25 | private notificationsService: NotificationsService, | 25 | private notifier: Notifier, |
26 | private videoAbuseService: VideoAbuseService, | 26 | private videoAbuseService: VideoAbuseService, |
27 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 27 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, |
28 | private i18n: I18n | 28 | private i18n: I18n |
@@ -45,29 +45,26 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
45 | }) | 45 | }) |
46 | } | 46 | } |
47 | 47 | ||
48 | hideModerationCommentModal () { | 48 | hide () { |
49 | this.abuseToComment = undefined | 49 | this.abuseToComment = undefined |
50 | this.openedModal.close() | 50 | this.openedModal.close() |
51 | this.form.reset() | 51 | this.form.reset() |
52 | } | 52 | } |
53 | 53 | ||
54 | async banUser () { | 54 | async banUser () { |
55 | const moderationComment: string = this.form.value['moderationComment'] | 55 | const moderationComment: string = this.form.value[ 'moderationComment' ] |
56 | 56 | ||
57 | this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) | 57 | this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) |
58 | .subscribe( | 58 | .subscribe( |
59 | () => { | 59 | () => { |
60 | this.notificationsService.success( | 60 | this.notifier.success(this.i18n('Comment updated.')) |
61 | this.i18n('Success'), | ||
62 | this.i18n('Comment updated.') | ||
63 | ) | ||
64 | 61 | ||
65 | this.commentUpdated.emit(moderationComment) | 62 | this.commentUpdated.emit(moderationComment) |
66 | this.hideModerationCommentModal() | 63 | this.hide() |
67 | }, | 64 | }, |
68 | 65 | ||
69 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 66 | err => this.notifier.error(err.message) |
70 | ) | 67 | ) |
71 | } | 68 | } |
72 | 69 | ||
73 | } | 70 | } |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 0374b70ef..05b549de6 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html | |||
@@ -41,7 +41,7 @@ | |||
41 | </td> | 41 | </td> |
42 | 42 | ||
43 | <td class="action-cell"> | 43 | <td class="action-cell"> |
44 | <my-action-dropdown i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown> | 44 | <my-action-dropdown placement="bottom-right" i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown> |
45 | </td> | 45 | </td> |
46 | </tr> | 46 | </tr> |
47 | </ng-template> | 47 | </ng-template> |
@@ -51,15 +51,15 @@ | |||
51 | <td class="moderation-expanded" colspan="6"> | 51 | <td class="moderation-expanded" colspan="6"> |
52 | <div> | 52 | <div> |
53 | <span i18n class="moderation-expanded-label">Reason:</span> | 53 | <span i18n class="moderation-expanded-label">Reason:</span> |
54 | <span class="moderation-expanded-text">{{ videoAbuse.reason }}</span> | 54 | <span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.reason)"></span> |
55 | </div> | 55 | </div> |
56 | <div *ngIf="videoAbuse.moderationComment"> | 56 | <div *ngIf="videoAbuse.moderationComment"> |
57 | <span i18n class="moderation-expanded-label">Moderation comment:</span> | 57 | <span i18n class="moderation-expanded-label">Moderation comment:</span> |
58 | <span class="moderation-expanded-text">{{ videoAbuse.moderationComment }}</span> | 58 | <span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.moderationComment)"></span> |
59 | </div> | 59 | </div> |
60 | </td> | 60 | </td> |
61 | </tr> | 61 | </tr> |
62 | </ng-template> | 62 | </ng-template> |
63 | </p-table> | 63 | </p-table> |
64 | 64 | ||
65 | <my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal> \ No newline at end of file | 65 | <my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index 7a219c846..00c871659 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { Account } from '../../../shared/account/account.model' | 2 | import { Account } from '../../../shared/account/account.model' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/components/common/sortmeta' | 4 | import { SortMeta } from 'primeng/components/common/sortmeta' |
5 | import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' | 5 | import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' |
6 | import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' | 6 | import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' |
@@ -9,6 +9,7 @@ import { DropdownAction } from '../../../shared/buttons/action-dropdown.componen | |||
9 | import { ConfirmService } from '../../../core/index' | 9 | import { ConfirmService } from '../../../core/index' |
10 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 10 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
11 | import { Video } from '../../../shared/video/video.model' | 11 | import { Video } from '../../../shared/video/video.model' |
12 | import { MarkdownService } from '@app/shared/renderer' | ||
12 | 13 | ||
13 | @Component({ | 14 | @Component({ |
14 | selector: 'my-video-abuse-list', | 15 | selector: 'my-video-abuse-list', |
@@ -27,10 +28,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
27 | videoAbuseActions: DropdownAction<VideoAbuse>[] = [] | 28 | videoAbuseActions: DropdownAction<VideoAbuse>[] = [] |
28 | 29 | ||
29 | constructor ( | 30 | constructor ( |
30 | private notificationsService: NotificationsService, | 31 | private notifier: Notifier, |
31 | private videoAbuseService: VideoAbuseService, | 32 | private videoAbuseService: VideoAbuseService, |
32 | private confirmService: ConfirmService, | 33 | private confirmService: ConfirmService, |
33 | private i18n: I18n | 34 | private i18n: I18n, |
35 | private markdownRenderer: MarkdownService | ||
34 | ) { | 36 | ) { |
35 | super() | 37 | super() |
36 | 38 | ||
@@ -90,14 +92,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
90 | 92 | ||
91 | this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( | 93 | this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( |
92 | () => { | 94 | () => { |
93 | this.notificationsService.success( | 95 | this.notifier.success(this.i18n('Abuse deleted.')) |
94 | this.i18n('Success'), | ||
95 | this.i18n('Abuse deleted.') | ||
96 | ) | ||
97 | this.loadData() | 96 | this.loadData() |
98 | }, | 97 | }, |
99 | 98 | ||
100 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 99 | err => this.notifier.error(err.message) |
101 | ) | 100 | ) |
102 | } | 101 | } |
103 | 102 | ||
@@ -106,11 +105,15 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
106 | .subscribe( | 105 | .subscribe( |
107 | () => this.loadData(), | 106 | () => this.loadData(), |
108 | 107 | ||
109 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 108 | err => this.notifier.error(err.message) |
110 | ) | 109 | ) |
111 | 110 | ||
112 | } | 111 | } |
113 | 112 | ||
113 | toHtml (text: string) { | ||
114 | return this.markdownRenderer.textMarkdownToHTML(text) | ||
115 | } | ||
116 | |||
114 | protected loadData () { | 117 | protected loadData () { |
115 | return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) | 118 | return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) |
116 | .subscribe( | 119 | .subscribe( |
@@ -119,7 +122,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
119 | this.totalRecords = resultList.total | 122 | this.totalRecords = resultList.total |
120 | }, | 123 | }, |
121 | 124 | ||
122 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 125 | err => this.notifier.error(err.message) |
123 | ) | 126 | ) |
124 | } | 127 | } |
125 | } | 128 | } |
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html index ff4543b97..247f441c1 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html | |||
@@ -7,6 +7,7 @@ | |||
7 | <th style="width: 40px"></th> | 7 | <th style="width: 40px"></th> |
8 | <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th> | 8 | <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th> |
9 | <th i18n>Sensitive</th> | 9 | <th i18n>Sensitive</th> |
10 | <th i18n>Unfederated</th> | ||
10 | <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> | 11 | <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> |
11 | <th style="width: 120px;"></th> | 12 | <th style="width: 120px;"></th> |
12 | </tr> | 13 | </tr> |
@@ -26,20 +27,21 @@ | |||
26 | </a> | 27 | </a> |
27 | </td> | 28 | </td> |
28 | 29 | ||
29 | <td>{{ videoBlacklist.video.nsfw }}</td> | 30 | <td>{{ booleanToText(videoBlacklist.video.nsfw) }}</td> |
31 | <td>{{ booleanToText(videoBlacklist.unfederated) }}</td> | ||
30 | <td>{{ videoBlacklist.createdAt }}</td> | 32 | <td>{{ videoBlacklist.createdAt }}</td> |
31 | 33 | ||
32 | <td class="action-cell"> | 34 | <td class="action-cell"> |
33 | <my-action-dropdown i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown> | 35 | <my-action-dropdown i18n-label placement="bottom-right" label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown> |
34 | </td> | 36 | </td> |
35 | </tr> | 37 | </tr> |
36 | </ng-template> | 38 | </ng-template> |
37 | 39 | ||
38 | <ng-template pTemplate="rowexpansion" let-videoBlacklist> | 40 | <ng-template pTemplate="rowexpansion" let-videoBlacklist> |
39 | <tr> | 41 | <tr> |
40 | <td class="moderation-expanded" colspan="5"> | 42 | <td class="moderation-expanded" colspan="6"> |
41 | <span i18n class="moderation-expanded-label">Blacklist reason:</span> | 43 | <span i18n class="moderation-expanded-label">Blacklist reason:</span> |
42 | <span class="moderation-expanded-text">{{ videoBlacklist.reason }}</span> | 44 | <span class="moderation-expanded-text" [innerHTML]="toHtml(videoBlacklist.reason)"></span> |
43 | </td> | 45 | </td> |
44 | </tr> | 46 | </tr> |
45 | </ng-template> | 47 | </ng-template> |
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index e491edaca..b27bbbfef 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { SortMeta } from 'primeng/components/common/sortmeta' | 2 | import { SortMeta } from 'primeng/components/common/sortmeta' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { ConfirmService } from '../../../core' | 4 | import { ConfirmService } from '../../../core' |
5 | import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' | 5 | import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' |
6 | import { VideoBlacklist } from '../../../../../../shared' | 6 | import { VideoBlacklist } from '../../../../../../shared' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' | 8 | import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' |
9 | import { Video } from '../../../shared/video/video.model' | 9 | import { Video } from '../../../shared/video/video.model' |
10 | import { MarkdownService } from '@app/shared/renderer' | ||
10 | 11 | ||
11 | @Component({ | 12 | @Component({ |
12 | selector: 'my-video-blacklist-list', | 13 | selector: 'my-video-blacklist-list', |
@@ -23,9 +24,10 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
23 | videoBlacklistActions: DropdownAction<VideoBlacklist>[] = [] | 24 | videoBlacklistActions: DropdownAction<VideoBlacklist>[] = [] |
24 | 25 | ||
25 | constructor ( | 26 | constructor ( |
26 | private notificationsService: NotificationsService, | 27 | private notifier: Notifier, |
27 | private confirmService: ConfirmService, | 28 | private confirmService: ConfirmService, |
28 | private videoBlacklistService: VideoBlacklistService, | 29 | private videoBlacklistService: VideoBlacklistService, |
30 | private markdownRenderer: MarkdownService, | ||
29 | private i18n: I18n | 31 | private i18n: I18n |
30 | ) { | 32 | ) { |
31 | super() | 33 | super() |
@@ -46,6 +48,16 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
46 | return Video.buildClientUrl(videoBlacklist.video.uuid) | 48 | return Video.buildClientUrl(videoBlacklist.video.uuid) |
47 | } | 49 | } |
48 | 50 | ||
51 | booleanToText (value: boolean) { | ||
52 | if (value === true) return this.i18n('yes') | ||
53 | |||
54 | return this.i18n('no') | ||
55 | } | ||
56 | |||
57 | toHtml (text: string) { | ||
58 | return this.markdownRenderer.textMarkdownToHTML(text) | ||
59 | } | ||
60 | |||
49 | async removeVideoFromBlacklist (entry: VideoBlacklist) { | 61 | async removeVideoFromBlacklist (entry: VideoBlacklist) { |
50 | const confirmMessage = this.i18n( | 62 | const confirmMessage = this.i18n( |
51 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' | 63 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' |
@@ -56,14 +68,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
56 | 68 | ||
57 | this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe( | 69 | this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe( |
58 | () => { | 70 | () => { |
59 | this.notificationsService.success( | 71 | this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })) |
60 | this.i18n('Success'), | ||
61 | this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name }) | ||
62 | ) | ||
63 | this.loadData() | 72 | this.loadData() |
64 | }, | 73 | }, |
65 | 74 | ||
66 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 75 | err => this.notifier.error(err.message) |
67 | ) | 76 | ) |
68 | } | 77 | } |
69 | 78 | ||
@@ -75,7 +84,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
75 | this.totalRecords = resultList.total | 84 | this.totalRecords = resultList.total |
76 | }, | 85 | }, |
77 | 86 | ||
78 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 87 | err => this.notifier.error(err.message) |
79 | ) | 88 | ) |
80 | } | 89 | } |
81 | } | 90 | } |
diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts index dd8e4efd5..137ecfcbd 100644 --- a/client/src/app/+admin/users/user-edit/user-create.component.ts +++ b/client/src/app/+admin/users/user-edit/user-create.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier, ServerService } from '@app/core' |
4 | import { ServerService } from '../../../core' | ||
5 | import { UserCreate, UserRole } from '../../../../../../shared' | 4 | import { UserCreate, UserRole } from '../../../../../../shared' |
6 | import { UserEdit } from './user-edit' | 5 | import { UserEdit } from './user-edit' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -24,7 +23,7 @@ export class UserCreateComponent extends UserEdit implements OnInit { | |||
24 | protected configService: ConfigService, | 23 | protected configService: ConfigService, |
25 | private userValidatorsService: UserValidatorsService, | 24 | private userValidatorsService: UserValidatorsService, |
26 | private router: Router, | 25 | private router: Router, |
27 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
28 | private userService: UserService, | 27 | private userService: UserService, |
29 | private i18n: I18n | 28 | private i18n: I18n |
30 | ) { | 29 | ) { |
@@ -60,10 +59,7 @@ export class UserCreateComponent extends UserEdit implements OnInit { | |||
60 | 59 | ||
61 | this.userService.addUser(userCreate).subscribe( | 60 | this.userService.addUser(userCreate).subscribe( |
62 | () => { | 61 | () => { |
63 | this.notificationsService.success( | 62 | this.notifier.success(this.i18n('User {{username}} created.', { username: userCreate.username })) |
64 | this.i18n('Success'), | ||
65 | this.i18n('User {{username}} created.', { username: userCreate.username }) | ||
66 | ) | ||
67 | this.router.navigate([ '/admin/users/list' ]) | 63 | this.router.navigate([ '/admin/users/list' ]) |
68 | }, | 64 | }, |
69 | 65 | ||
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts index cd3885a99..61e641823 100644 --- a/client/src/app/+admin/users/user-edit/user-update.component.ts +++ b/client/src/app/+admin/users/user-edit/user-update.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Subscription } from 'rxjs' | 3 | import { Subscription } from 'rxjs' |
4 | import { NotificationsService } from 'angular2-notifications' | 4 | import { Notifier } from '@app/core' |
5 | import { ServerService } from '../../../core' | 5 | import { ServerService } from '../../../core' |
6 | import { UserEdit } from './user-edit' | 6 | import { UserEdit } from './user-edit' |
7 | import { User, UserUpdate } from '../../../../../../shared' | 7 | import { User, UserUpdate } from '../../../../../../shared' |
@@ -30,7 +30,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
30 | private userValidatorsService: UserValidatorsService, | 30 | private userValidatorsService: UserValidatorsService, |
31 | private route: ActivatedRoute, | 31 | private route: ActivatedRoute, |
32 | private router: Router, | 32 | private router: Router, |
33 | private notificationsService: NotificationsService, | 33 | private notifier: Notifier, |
34 | private userService: UserService, | 34 | private userService: UserService, |
35 | private i18n: I18n | 35 | private i18n: I18n |
36 | ) { | 36 | ) { |
@@ -73,10 +73,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
73 | 73 | ||
74 | this.userService.updateUser(this.userId, userUpdate).subscribe( | 74 | this.userService.updateUser(this.userId, userUpdate).subscribe( |
75 | () => { | 75 | () => { |
76 | this.notificationsService.success( | 76 | this.notifier.success(this.i18n('User {{username}} updated.', { username: this.username })) |
77 | this.i18n('Success'), | ||
78 | this.i18n('User {{username}} updated.', { username: this.username }) | ||
79 | ) | ||
80 | this.router.navigate([ '/admin/users/list' ]) | 77 | this.router.navigate([ '/admin/users/list' ]) |
81 | }, | 78 | }, |
82 | 79 | ||
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index 556ab3c5d..69a4616a3 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <div i18n class="form-sub-title">Users list</div> | 2 | <div i18n class="form-sub-title">Users list</div> |
3 | 3 | ||
4 | <a class="add-button" routerLink="/admin/users/create"> | 4 | <a class="add-button" routerLink="/admin/users/create"> |
5 | <span class="icon icon-add"></span> | 5 | <my-global-icon iconName="add"></my-global-icon> |
6 | <ng-container i18n>Create user</ng-container> | 6 | <ng-container i18n>Create user</ng-container> |
7 | </a> | 7 | </a> |
8 | </div> | 8 | </div> |
@@ -65,7 +65,9 @@ | |||
65 | <span i18n *ngIf="user.blocked" class="banned-info">(banned)</span> | 65 | <span i18n *ngIf="user.blocked" class="banned-info">(banned)</span> |
66 | </a> | 66 | </a> |
67 | </td> | 67 | </td> |
68 | |||
68 | <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">{{ user.email }}</td> | 69 | <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">{{ user.email }}</td> |
70 | |||
69 | <ng-template #emailWithVerificationStatus> | 71 | <ng-template #emailWithVerificationStatus> |
70 | <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login"> | 72 | <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login"> |
71 | <em>? {{ user.email }}</em> | 73 | <em>? {{ user.email }}</em> |
@@ -76,6 +78,7 @@ | |||
76 | </td> | 78 | </td> |
77 | </ng-template> | 79 | </ng-template> |
78 | </ng-template> | 80 | </ng-template> |
81 | |||
79 | <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td> | 82 | <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td> |
80 | <td>{{ user.roleLabel }}</td> | 83 | <td>{{ user.roleLabel }}</td> |
81 | <td>{{ user.createdAt }}</td> | 84 | <td>{{ user.createdAt }}</td> |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss index f235769f0..5274be01c 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/users/user-list/user-list.component.scss | |||
@@ -2,7 +2,7 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .add-button { | 4 | .add-button { |
5 | @include create-button('../../../../assets/images/global/add.svg'); | 5 | @include create-button; |
6 | } | 6 | } |
7 | 7 | ||
8 | tr.banned { | 8 | tr.banned { |
@@ -23,4 +23,4 @@ tr.banned { | |||
23 | input { | 23 | input { |
24 | @include peertube-input-text(250px); | 24 | @include peertube-input-text(250px); |
25 | } | 25 | } |
26 | } \ No newline at end of file | 26 | } |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index fb085c133..66ab796f9 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { SortMeta } from 'primeng/components/common/sortmeta' | 3 | import { SortMeta } from 'primeng/components/common/sortmeta' |
4 | import { ConfirmService, ServerService } from '../../../core' | 4 | import { ConfirmService, ServerService } from '../../../core' |
5 | import { RestPagination, RestTable, UserService } from '../../../shared' | 5 | import { RestPagination, RestTable, UserService } from '../../../shared' |
@@ -26,7 +26,7 @@ export class UserListComponent extends RestTable implements OnInit { | |||
26 | bulkUserActions: DropdownAction<User[]>[] = [] | 26 | bulkUserActions: DropdownAction<User[]>[] = [] |
27 | 27 | ||
28 | constructor ( | 28 | constructor ( |
29 | private notificationsService: NotificationsService, | 29 | private notifier: Notifier, |
30 | private confirmService: ConfirmService, | 30 | private confirmService: ConfirmService, |
31 | private serverService: ServerService, | 31 | private serverService: ServerService, |
32 | private userService: UserService, | 32 | private userService: UserService, |
@@ -68,7 +68,7 @@ export class UserListComponent extends RestTable implements OnInit { | |||
68 | openBanUserModal (users: User[]) { | 68 | openBanUserModal (users: User[]) { |
69 | for (const user of users) { | 69 | for (const user of users) { |
70 | if (user.username === 'root') { | 70 | if (user.username === 'root') { |
71 | this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.')) | 71 | this.notifier.error(this.i18n('You cannot ban root.')) |
72 | return | 72 | return |
73 | } | 73 | } |
74 | } | 74 | } |
@@ -91,18 +91,18 @@ export class UserListComponent extends RestTable implements OnInit { | |||
91 | () => { | 91 | () => { |
92 | const message = this.i18n('{{num}} users unbanned.', { num: users.length }) | 92 | const message = this.i18n('{{num}} users unbanned.', { num: users.length }) |
93 | 93 | ||
94 | this.notificationsService.success(this.i18n('Success'), message) | 94 | this.notifier.success(message) |
95 | this.loadData() | 95 | this.loadData() |
96 | }, | 96 | }, |
97 | 97 | ||
98 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 98 | err => this.notifier.error(err.message) |
99 | ) | 99 | ) |
100 | } | 100 | } |
101 | 101 | ||
102 | async removeUsers (users: User[]) { | 102 | async removeUsers (users: User[]) { |
103 | for (const user of users) { | 103 | for (const user of users) { |
104 | if (user.username === 'root') { | 104 | if (user.username === 'root') { |
105 | this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.')) | 105 | this.notifier.error(this.i18n('You cannot delete root.')) |
106 | return | 106 | return |
107 | } | 107 | } |
108 | } | 108 | } |
@@ -113,28 +113,22 @@ export class UserListComponent extends RestTable implements OnInit { | |||
113 | 113 | ||
114 | this.userService.removeUser(users).subscribe( | 114 | this.userService.removeUser(users).subscribe( |
115 | () => { | 115 | () => { |
116 | this.notificationsService.success( | 116 | this.notifier.success(this.i18n('{{num}} users deleted.', { num: users.length })) |
117 | this.i18n('Success'), | ||
118 | this.i18n('{{num}} users deleted.', { num: users.length }) | ||
119 | ) | ||
120 | this.loadData() | 117 | this.loadData() |
121 | }, | 118 | }, |
122 | 119 | ||
123 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 120 | err => this.notifier.error(err.message) |
124 | ) | 121 | ) |
125 | } | 122 | } |
126 | 123 | ||
127 | async setEmailsAsVerified (users: User[]) { | 124 | async setEmailsAsVerified (users: User[]) { |
128 | this.userService.updateUsers(users, { emailVerified: true }).subscribe( | 125 | this.userService.updateUsers(users, { emailVerified: true }).subscribe( |
129 | () => { | 126 | () => { |
130 | this.notificationsService.success( | 127 | this.notifier.success(this.i18n('{{num}} users email set as verified.', { num: users.length })) |
131 | this.i18n('Success'), | ||
132 | this.i18n('{{num}} users email set as verified.', { num: users.length }) | ||
133 | ) | ||
134 | this.loadData() | 128 | this.loadData() |
135 | }, | 129 | }, |
136 | 130 | ||
137 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 131 | err => this.notifier.error(err.message) |
138 | ) | 132 | ) |
139 | } | 133 | } |
140 | 134 | ||
@@ -146,13 +140,13 @@ export class UserListComponent extends RestTable implements OnInit { | |||
146 | this.selectedUsers = [] | 140 | this.selectedUsers = [] |
147 | 141 | ||
148 | this.userService.getUsers(this.pagination, this.sort, this.search) | 142 | this.userService.getUsers(this.pagination, this.sort, this.search) |
149 | .subscribe( | 143 | .subscribe( |
150 | resultList => { | 144 | resultList => { |
151 | this.users = resultList.data | 145 | this.users = resultList.data |
152 | this.totalRecords = resultList.total | 146 | this.totalRecords = resultList.total |
153 | }, | 147 | }, |
154 | 148 | ||
155 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 149 | err => this.notifier.error(err.message) |
156 | ) | 150 | ) |
157 | } | 151 | } |
158 | } | 152 | } |
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts index fbad28410..e3025dec4 100644 --- a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts +++ b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RestPagination, RestTable } from '@app/shared' | 4 | import { RestPagination, RestTable } from '@app/shared' |
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | 5 | import { SortMeta } from 'primeng/components/common/sortmeta' |
6 | import { BlocklistService, AccountBlock } from '@app/shared/blocklist' | 6 | import { AccountBlock, BlocklistService } from '@app/shared/blocklist' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-account-blocklist', | 9 | selector: 'my-account-blocklist', |
@@ -18,7 +18,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit { | |||
18 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 18 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
19 | 19 | ||
20 | constructor ( | 20 | constructor ( |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private blocklistService: BlocklistService, | 22 | private blocklistService: BlocklistService, |
23 | private i18n: I18n | 23 | private i18n: I18n |
24 | ) { | 24 | ) { |
@@ -35,10 +35,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit { | |||
35 | this.blocklistService.unblockAccountByUser(blockedAccount) | 35 | this.blocklistService.unblockAccountByUser(blockedAccount) |
36 | .subscribe( | 36 | .subscribe( |
37 | () => { | 37 | () => { |
38 | this.notificationsService.success( | 38 | this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost })) |
39 | this.i18n('Success'), | ||
40 | this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost }) | ||
41 | ) | ||
42 | 39 | ||
43 | this.loadData() | 40 | this.loadData() |
44 | } | 41 | } |
@@ -53,7 +50,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit { | |||
53 | this.totalRecords = resultList.total | 50 | this.totalRecords = resultList.total |
54 | }, | 51 | }, |
55 | 52 | ||
56 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 53 | err => this.notifier.error(err.message) |
57 | ) | 54 | ) |
58 | } | 55 | } |
59 | } | 56 | } |
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts index b411d6926..4c5cc28b8 100644 --- a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts +++ b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RestPagination, RestTable } from '@app/shared' | 4 | import { RestPagination, RestTable } from '@app/shared' |
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | 5 | import { SortMeta } from 'primeng/components/common/sortmeta' |
@@ -19,7 +19,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn | |||
19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
20 | 20 | ||
21 | constructor ( | 21 | constructor ( |
22 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
23 | private blocklistService: BlocklistService, | 23 | private blocklistService: BlocklistService, |
24 | private i18n: I18n | 24 | private i18n: I18n |
25 | ) { | 25 | ) { |
@@ -36,10 +36,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn | |||
36 | this.blocklistService.unblockServerByUser(host) | 36 | this.blocklistService.unblockServerByUser(host) |
37 | .subscribe( | 37 | .subscribe( |
38 | () => { | 38 | () => { |
39 | this.notificationsService.success( | 39 | this.notifier.success(this.i18n('Instance {{host}} unmuted.', { host })) |
40 | this.i18n('Success'), | ||
41 | this.i18n('Instance {{host}} unmuted.', { host }) | ||
42 | ) | ||
43 | 40 | ||
44 | this.loadData() | 41 | this.loadData() |
45 | } | 42 | } |
@@ -54,7 +51,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn | |||
54 | this.totalRecords = resultList.total | 51 | this.totalRecords = resultList.total |
55 | }, | 52 | }, |
56 | 53 | ||
57 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 54 | err => this.notifier.error(err.message) |
58 | ) | 55 | ) |
59 | } | 56 | } |
60 | } | 57 | } |
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.html b/client/src/app/+my-account/my-account-history/my-account-history.component.html new file mode 100644 index 000000000..d42af37d4 --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.html | |||
@@ -0,0 +1,27 @@ | |||
1 | <div class="top-buttons"> | ||
2 | <div class="history-switch"> | ||
3 | <p-inputSwitch [(ngModel)]="videosHistoryEnabled" (ngModelChange)="onVideosHistoryChange()"></p-inputSwitch> | ||
4 | <label i18n>History enabled</label> | ||
5 | </div> | ||
6 | |||
7 | <div class="delete-history"> | ||
8 | <button (click)="deleteHistory()" i18n>Delete history</button> | ||
9 | </div> | ||
10 | </div> | ||
11 | |||
12 | |||
13 | <div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div> | ||
14 | |||
15 | <div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" class="videos" #videosElement> | ||
16 | <div *ngFor="let videos of videoPages;" class="videos-page"> | ||
17 | <div class="video" *ngFor="let video of videos"> | ||
18 | <my-video-thumbnail [video]="video"></my-video-thumbnail> | ||
19 | |||
20 | <div class="video-info"> | ||
21 | <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> | ||
22 | <span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span> | ||
23 | <a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a> | ||
24 | </div> | ||
25 | </div> | ||
26 | </div> | ||
27 | </div> | ||
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.scss b/client/src/app/+my-account/my-account-history/my-account-history.component.scss new file mode 100644 index 000000000..e7c6863f1 --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.scss | |||
@@ -0,0 +1,99 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .no-history { | ||
5 | display: flex; | ||
6 | justify-content: center; | ||
7 | margin-top: 50px; | ||
8 | font-weight: $font-semibold; | ||
9 | font-size: 16px; | ||
10 | } | ||
11 | |||
12 | .top-buttons { | ||
13 | margin-bottom: 20px; | ||
14 | display: flex; | ||
15 | |||
16 | .history-switch { | ||
17 | display: flex; | ||
18 | flex-grow: 1; | ||
19 | |||
20 | label { | ||
21 | margin: 0 0 0 5px; | ||
22 | } | ||
23 | } | ||
24 | |||
25 | .delete-history { | ||
26 | font-size: 15px; | ||
27 | |||
28 | button { | ||
29 | @include peertube-button; | ||
30 | @include grey-button; | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | |||
35 | .video { | ||
36 | @include row-blocks; | ||
37 | |||
38 | my-video-thumbnail { | ||
39 | margin-right: 10px; | ||
40 | } | ||
41 | |||
42 | .video-info { | ||
43 | flex-grow: 1; | ||
44 | |||
45 | .video-info-name { | ||
46 | @include disable-default-a-behaviour; | ||
47 | |||
48 | color: var(--mainForegroundColor); | ||
49 | display: block; | ||
50 | width: fit-content; | ||
51 | font-size: 18px; | ||
52 | font-weight: $font-semibold; | ||
53 | } | ||
54 | |||
55 | .video-info-date-views { | ||
56 | font-size: 14px; | ||
57 | } | ||
58 | |||
59 | .video-info-account { | ||
60 | @include disable-default-a-behaviour; | ||
61 | |||
62 | display: block; | ||
63 | width: fit-content; | ||
64 | overflow: hidden; | ||
65 | text-overflow: ellipsis; | ||
66 | white-space: nowrap; | ||
67 | font-size: 14px; | ||
68 | color: $grey-foreground-color; | ||
69 | |||
70 | &:hover { | ||
71 | color: $grey-foreground-hover-color; | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | @media screen and (max-width: $small-view) { | ||
78 | .video { | ||
79 | flex-direction: column; | ||
80 | height: auto; | ||
81 | text-align: center; | ||
82 | |||
83 | .video-info-name { | ||
84 | margin: auto; | ||
85 | } | ||
86 | |||
87 | input[type=checkbox] { | ||
88 | display: none; | ||
89 | } | ||
90 | |||
91 | my-video-thumbnail { | ||
92 | margin-right: 0; | ||
93 | } | ||
94 | |||
95 | .video-buttons { | ||
96 | margin-top: 10px; | ||
97 | } | ||
98 | } | ||
99 | } | ||
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts new file mode 100644 index 000000000..394091bad --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.ts | |||
@@ -0,0 +1,107 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Location } from '@angular/common' | ||
4 | import { immutableAssign } from '@app/shared/misc/utils' | ||
5 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
6 | import { AuthService } from '../../core/auth' | ||
7 | import { ConfirmService } from '../../core/confirm' | ||
8 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | ||
9 | import { VideoService } from '../../shared/video/video.service' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
11 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
12 | import { UserHistoryService } from '@app/shared/users/user-history.service' | ||
13 | import { UserService } from '@app/shared' | ||
14 | import { Notifier } from '@app/core' | ||
15 | |||
16 | @Component({ | ||
17 | selector: 'my-account-history', | ||
18 | templateUrl: './my-account-history.component.html', | ||
19 | styleUrls: [ './my-account-history.component.scss' ] | ||
20 | }) | ||
21 | export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
22 | titlePage: string | ||
23 | currentRoute = '/my-account/history/videos' | ||
24 | pagination: ComponentPagination = { | ||
25 | currentPage: 1, | ||
26 | itemsPerPage: 5, | ||
27 | totalItems: null | ||
28 | } | ||
29 | videosHistoryEnabled: boolean | ||
30 | |||
31 | protected baseVideoWidth = -1 | ||
32 | protected baseVideoHeight = 155 | ||
33 | |||
34 | constructor ( | ||
35 | protected router: Router, | ||
36 | protected route: ActivatedRoute, | ||
37 | protected authService: AuthService, | ||
38 | protected userService: UserService, | ||
39 | protected notifier: Notifier, | ||
40 | protected location: Location, | ||
41 | protected screenService: ScreenService, | ||
42 | protected i18n: I18n, | ||
43 | private confirmService: ConfirmService, | ||
44 | private videoService: VideoService, | ||
45 | private userHistoryService: UserHistoryService | ||
46 | ) { | ||
47 | super() | ||
48 | |||
49 | this.titlePage = this.i18n('My videos history') | ||
50 | } | ||
51 | |||
52 | ngOnInit () { | ||
53 | super.ngOnInit() | ||
54 | |||
55 | this.videosHistoryEnabled = this.authService.getUser().videosHistoryEnabled | ||
56 | } | ||
57 | |||
58 | ngOnDestroy () { | ||
59 | super.ngOnDestroy() | ||
60 | } | ||
61 | |||
62 | getVideosObservable (page: number) { | ||
63 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | ||
64 | |||
65 | return this.userHistoryService.getUserVideosHistory(newPagination) | ||
66 | } | ||
67 | |||
68 | generateSyndicationList () { | ||
69 | throw new Error('Method not implemented.') | ||
70 | } | ||
71 | |||
72 | onVideosHistoryChange () { | ||
73 | this.userService.updateMyProfile({ videosHistoryEnabled: this.videosHistoryEnabled }) | ||
74 | .subscribe( | ||
75 | () => { | ||
76 | const message = this.videosHistoryEnabled === true ? | ||
77 | this.i18n('Videos history is enabled') : | ||
78 | this.i18n('Videos history is disabled') | ||
79 | |||
80 | this.notifier.success(message) | ||
81 | |||
82 | this.authService.refreshUserInformation() | ||
83 | }, | ||
84 | |||
85 | err => this.notifier.error(err.message) | ||
86 | ) | ||
87 | } | ||
88 | |||
89 | async deleteHistory () { | ||
90 | const title = this.i18n('Delete videos history') | ||
91 | const message = this.i18n('Are you sure you want to delete all your videos history?') | ||
92 | |||
93 | const res = await this.confirmService.confirm(message, title) | ||
94 | if (res !== true) return | ||
95 | |||
96 | this.userHistoryService.deleteUserVideosHistory() | ||
97 | .subscribe( | ||
98 | () => { | ||
99 | this.notifier.success(this.i18n('Videos history deleted')) | ||
100 | |||
101 | this.reloadVideos() | ||
102 | }, | ||
103 | |||
104 | err => this.notifier.error(err.message) | ||
105 | ) | ||
106 | } | ||
107 | } | ||
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html new file mode 100644 index 000000000..d518b22ec --- /dev/null +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html | |||
@@ -0,0 +1,13 @@ | |||
1 | <div class="header"> | ||
2 | <a routerLink="/my-account/settings" fragment="notifications" i18n> | ||
3 | <my-global-icon iconName="cog"></my-global-icon> | ||
4 | Notification preferences | ||
5 | </a> | ||
6 | |||
7 | <button (click)="markAllAsRead()" i18n> | ||
8 | <my-global-icon iconName="circle-tick"></my-global-icon> | ||
9 | Mark all as read | ||
10 | </button> | ||
11 | </div> | ||
12 | |||
13 | <my-user-notifications #userNotification></my-user-notifications> | ||
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss new file mode 100644 index 000000000..43d1f82ab --- /dev/null +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss | |||
@@ -0,0 +1,25 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .header { | ||
5 | display: flex; | ||
6 | justify-content: space-between; | ||
7 | font-size: 15px; | ||
8 | margin-bottom: 20px; | ||
9 | |||
10 | a { | ||
11 | @include peertube-button-link; | ||
12 | @include grey-button; | ||
13 | @include button-with-icon(18px, 3px, -1px); | ||
14 | } | ||
15 | |||
16 | button { | ||
17 | @include peertube-button; | ||
18 | @include grey-button; | ||
19 | @include button-with-icon(20px, 3px, -1px); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | my-user-notifications { | ||
24 | font-size: 15px; | ||
25 | } | ||
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts new file mode 100644 index 000000000..3e197088d --- /dev/null +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts | |||
@@ -0,0 +1,14 @@ | |||
1 | import { Component, ViewChild } from '@angular/core' | ||
2 | import { UserNotificationsComponent } from '@app/shared' | ||
3 | |||
4 | @Component({ | ||
5 | templateUrl: './my-account-notifications.component.html', | ||
6 | styleUrls: [ './my-account-notifications.component.scss' ] | ||
7 | }) | ||
8 | export class MyAccountNotificationsComponent { | ||
9 | @ViewChild('userNotification') userNotification: UserNotificationsComponent | ||
10 | |||
11 | markAllAsRead () { | ||
12 | this.userNotification.markAllAsRead() | ||
13 | } | ||
14 | } | ||
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html index fd7d7d23b..674a4e8a2 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html +++ b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html | |||
@@ -1,7 +1,8 @@ | |||
1 | <ng-template #modal let-close="close" let-dismiss="dismiss"> | 1 | <ng-template #modal let-close="close" let-dismiss="dismiss"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Accept ownership</h4> | 3 | <h4 i18n class="modal-title">Accept ownership</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span> | 4 | |
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon> | ||
5 | </div> | 6 | </div> |
6 | 7 | ||
7 | <div class="modal-body" [formGroup]="form"> | 8 | <div class="modal-body" [formGroup]="form"> |
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts index a68b452ec..79d29b139 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts +++ b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { AuthService, Notifier } from '@app/core' |
3 | import { FormReactive } from '@app/shared' | 3 | import { FormReactive } from '@app/shared' |
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
5 | import { VideoOwnershipService } from '@app/shared/video-ownership' | 5 | import { VideoOwnershipService } from '@app/shared/video-ownership' |
@@ -8,7 +8,6 @@ import { VideoAcceptOwnershipValidatorsService } from '@app/shared/forms/form-va | |||
8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { AuthService } from '@app/core' | ||
12 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 11 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
13 | 12 | ||
14 | @Component({ | 13 | @Component({ |
@@ -31,7 +30,7 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O | |||
31 | protected formValidatorService: FormValidatorService, | 30 | protected formValidatorService: FormValidatorService, |
32 | private videoChangeOwnershipValidatorsService: VideoAcceptOwnershipValidatorsService, | 31 | private videoChangeOwnershipValidatorsService: VideoAcceptOwnershipValidatorsService, |
33 | private videoOwnershipService: VideoOwnershipService, | 32 | private videoOwnershipService: VideoOwnershipService, |
34 | private notificationsService: NotificationsService, | 33 | private notifier: Notifier, |
35 | private authService: AuthService, | 34 | private authService: AuthService, |
36 | private videoChannelService: VideoChannelService, | 35 | private videoChannelService: VideoChannelService, |
37 | private modalService: NgbModal, | 36 | private modalService: NgbModal, |
@@ -68,12 +67,12 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O | |||
68 | .acceptOwnership(videoChangeOwnership.id, { channelId: channel }) | 67 | .acceptOwnership(videoChangeOwnership.id, { channelId: channel }) |
69 | .subscribe( | 68 | .subscribe( |
70 | () => { | 69 | () => { |
71 | this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership accepted')) | 70 | this.notifier.success(this.i18n('Ownership accepted')) |
72 | if (this.accepted) this.accepted.emit() | 71 | if (this.accepted) this.accepted.emit() |
73 | this.videoChangeOwnership = undefined | 72 | this.videoChangeOwnership = undefined |
74 | }, | 73 | }, |
75 | 74 | ||
76 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 75 | err => this.notifier.error(err.message) |
77 | ) | 76 | ) |
78 | } | 77 | } |
79 | } | 78 | } |
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html index 379fd8bb1..5709e9f54 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html +++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html | |||
@@ -40,10 +40,10 @@ | |||
40 | <td class="action-cell"> | 40 | <td class="action-cell"> |
41 | <ng-container *ngIf="videoChangeOwnership.status === 'WAITING'"> | 41 | <ng-container *ngIf="videoChangeOwnership.status === 'WAITING'"> |
42 | <my-button i18n label="Accept" | 42 | <my-button i18n label="Accept" |
43 | icon="icon-tick" | 43 | icon="tick" |
44 | (click)="openAcceptModal(videoChangeOwnership)"></my-button> | 44 | (click)="openAcceptModal(videoChangeOwnership)"></my-button> |
45 | <my-button i18n label="Refuse" | 45 | <my-button i18n label="Refuse" |
46 | icon="icon-cross" | 46 | icon="cross" |
47 | (click)="refuse(videoChangeOwnership)">Refuse</my-button> | 47 | (click)="refuse(videoChangeOwnership)">Refuse</my-button> |
48 | </ng-container> | 48 | </ng-container> |
49 | </td> | 49 | </td> |
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts index 0b51ac13c..77857c4fd 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts +++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts | |||
@@ -1,13 +1,11 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { RestPagination, RestTable } from '@app/shared' | 3 | import { RestPagination, RestTable } from '@app/shared' |
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | 4 | import { SortMeta } from 'primeng/components/common/sortmeta' |
6 | import { VideoChangeOwnership } from '../../../../../shared' | 5 | import { VideoChangeOwnership } from '../../../../../shared' |
7 | import { VideoOwnershipService } from '@app/shared/video-ownership' | 6 | import { VideoOwnershipService } from '@app/shared/video-ownership' |
8 | import { Account } from '@app/shared/account/account.model' | 7 | import { Account } from '@app/shared/account/account.model' |
9 | import { MyAccountAcceptOwnershipComponent } | 8 | import { MyAccountAcceptOwnershipComponent } from './my-account-accept-ownership/my-account-accept-ownership.component' |
10 | from '@app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component' | ||
11 | 9 | ||
12 | @Component({ | 10 | @Component({ |
13 | selector: 'my-account-ownership', | 11 | selector: 'my-account-ownership', |
@@ -23,9 +21,8 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit { | |||
23 | @ViewChild('myAccountAcceptOwnershipComponent') myAccountAcceptOwnershipComponent: MyAccountAcceptOwnershipComponent | 21 | @ViewChild('myAccountAcceptOwnershipComponent') myAccountAcceptOwnershipComponent: MyAccountAcceptOwnershipComponent |
24 | 22 | ||
25 | constructor ( | 23 | constructor ( |
26 | private notificationsService: NotificationsService, | 24 | private notifier: Notifier, |
27 | private videoOwnershipService: VideoOwnershipService, | 25 | private videoOwnershipService: VideoOwnershipService |
28 | private i18n: I18n | ||
29 | ) { | 26 | ) { |
30 | super() | 27 | super() |
31 | } | 28 | } |
@@ -50,7 +47,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit { | |||
50 | this.videoOwnershipService.refuseOwnership(videoChangeOwnership.id) | 47 | this.videoOwnershipService.refuseOwnership(videoChangeOwnership.id) |
51 | .subscribe( | 48 | .subscribe( |
52 | () => this.loadData(), | 49 | () => this.loadData(), |
53 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 50 | err => this.notifier.error(err.message) |
54 | ) | 51 | ) |
55 | } | 52 | } |
56 | 53 | ||
@@ -62,7 +59,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit { | |||
62 | this.totalRecords = resultList.total | 59 | this.totalRecords = resultList.total |
63 | }, | 60 | }, |
64 | 61 | ||
65 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 62 | err => this.notifier.error(err.message) |
66 | ) | 63 | ) |
67 | } | 64 | } |
68 | } | 65 | } |
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts index 601e517b4..9996218ca 100644 --- a/client/src/app/+my-account/my-account-routing.module.ts +++ b/client/src/app/+my-account/my-account-routing.module.ts | |||
@@ -13,6 +13,8 @@ import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-sub | |||
13 | import { MyAccountOwnershipComponent } from '@app/+my-account/my-account-ownership/my-account-ownership.component' | 13 | import { MyAccountOwnershipComponent } from '@app/+my-account/my-account-ownership/my-account-ownership.component' |
14 | import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component' | 14 | import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component' |
15 | import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' | 15 | import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' |
16 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' | ||
17 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' | ||
16 | 18 | ||
17 | const myAccountRoutes: Routes = [ | 19 | const myAccountRoutes: Routes = [ |
18 | { | 20 | { |
@@ -114,6 +116,24 @@ const myAccountRoutes: Routes = [ | |||
114 | title: 'Muted instances' | 116 | title: 'Muted instances' |
115 | } | 117 | } |
116 | } | 118 | } |
119 | }, | ||
120 | { | ||
121 | path: 'history/videos', | ||
122 | component: MyAccountHistoryComponent, | ||
123 | data: { | ||
124 | meta: { | ||
125 | title: 'Videos history' | ||
126 | } | ||
127 | } | ||
128 | }, | ||
129 | { | ||
130 | path: 'notifications', | ||
131 | component: MyAccountNotificationsComponent, | ||
132 | data: { | ||
133 | meta: { | ||
134 | title: 'Notifications' | ||
135 | } | ||
136 | } | ||
117 | } | 137 | } |
118 | ] | 138 | ] |
119 | } | 139 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts index e5343b33d..cbb068c7c 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { AuthService, Notifier } from '@app/core' |
3 | import { FormReactive, UserService } from '../../../shared' | 3 | import { FormReactive, UserService } from '../../../shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 5 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
6 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' | 6 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' |
7 | import { filter } from 'rxjs/operators' | 7 | import { filter } from 'rxjs/operators' |
8 | import { AuthService } from '@app/core' | ||
9 | import { User } from '../../../../../../shared' | 8 | import { User } from '../../../../../../shared' |
10 | 9 | ||
11 | @Component({ | 10 | @Component({ |
@@ -20,7 +19,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On | |||
20 | constructor ( | 19 | constructor ( |
21 | protected formValidatorService: FormValidatorService, | 20 | protected formValidatorService: FormValidatorService, |
22 | private userValidatorsService: UserValidatorsService, | 21 | private userValidatorsService: UserValidatorsService, |
23 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
24 | private authService: AuthService, | 23 | private authService: AuthService, |
25 | private userService: UserService, | 24 | private userService: UserService, |
26 | private i18n: I18n | 25 | private i18n: I18n |
@@ -50,7 +49,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On | |||
50 | 49 | ||
51 | this.userService.changePassword(currentPassword, newPassword).subscribe( | 50 | this.userService.changePassword(currentPassword, newPassword).subscribe( |
52 | () => { | 51 | () => { |
53 | this.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.')) | 52 | this.notifier.success(this.i18n('Password updated.')) |
54 | 53 | ||
55 | this.form.reset() | 54 | this.form.reset() |
56 | this.error = null | 55 | this.error = null |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts index 63a121f64..3f79efe20 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { AuthService, ConfirmService, RedirectService } from '../../../core' | 3 | import { AuthService, ConfirmService, RedirectService } from '../../../core' |
4 | import { UserService } from '../../../shared' | 4 | import { UserService } from '../../../shared' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -15,7 +15,7 @@ export class MyAccountDangerZoneComponent { | |||
15 | 15 | ||
16 | constructor ( | 16 | constructor ( |
17 | private authService: AuthService, | 17 | private authService: AuthService, |
18 | private notificationsService: NotificationsService, | 18 | private notifier: Notifier, |
19 | private userService: UserService, | 19 | private userService: UserService, |
20 | private confirmService: ConfirmService, | 20 | private confirmService: ConfirmService, |
21 | private redirectService: RedirectService, | 21 | private redirectService: RedirectService, |
@@ -34,13 +34,13 @@ export class MyAccountDangerZoneComponent { | |||
34 | 34 | ||
35 | this.userService.deleteMe().subscribe( | 35 | this.userService.deleteMe().subscribe( |
36 | () => { | 36 | () => { |
37 | this.notificationsService.success(this.i18n('Success'), this.i18n('Your account is deleted.')) | 37 | this.notifier.success(this.i18n('Your account is deleted.')) |
38 | 38 | ||
39 | this.authService.logout() | 39 | this.authService.logout() |
40 | this.redirectService.redirectToHomepage() | 40 | this.redirectService.redirectToHomepage() |
41 | }, | 41 | }, |
42 | 42 | ||
43 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 43 | err => this.notifier.error(err.message) |
44 | ) | 44 | ) |
45 | } | 45 | } |
46 | } | 46 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/index.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/index.ts new file mode 100644 index 000000000..5e1d51339 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './my-account-notification-preferences.component' | |||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html new file mode 100644 index 000000000..59422d682 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html | |||
@@ -0,0 +1,19 @@ | |||
1 | <div class="custom-row"> | ||
2 | <div i18n>Activities</div> | ||
3 | <div i18n>Web</div> | ||
4 | <div i18n *ngIf="emailEnabled">Email</div> | ||
5 | </div> | ||
6 | |||
7 | <div class="custom-row" *ngFor="let notificationType of notificationSettingKeys"> | ||
8 | <ng-container *ngIf="hasUserRight(notificationType)"> | ||
9 | <div>{{ labelNotifications[notificationType] }}</div> | ||
10 | |||
11 | <div> | ||
12 | <p-inputSwitch [(ngModel)]="webNotifications[notificationType]" (onChange)="updateWebSetting(notificationType, $event.checked)"></p-inputSwitch> | ||
13 | </div> | ||
14 | |||
15 | <div *ngIf="emailEnabled"> | ||
16 | <p-inputSwitch [(ngModel)]="emailNotifications[notificationType]" (onChange)="updateEmailSetting(notificationType, $event.checked)"></p-inputSwitch> | ||
17 | </div> | ||
18 | </ng-container> | ||
19 | </div> | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss new file mode 100644 index 000000000..6feb16ab1 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss | |||
@@ -0,0 +1,25 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .custom-row { | ||
5 | display: flex; | ||
6 | align-items: center; | ||
7 | border-bottom: 1px solid rgba(0, 0, 0, 0.10); | ||
8 | |||
9 | &:first-child { | ||
10 | font-size: 16px; | ||
11 | |||
12 | & > div { | ||
13 | font-weight: $font-semibold; | ||
14 | } | ||
15 | } | ||
16 | |||
17 | & > div { | ||
18 | width: 350px; | ||
19 | } | ||
20 | |||
21 | & > div { | ||
22 | padding: 10px | ||
23 | } | ||
24 | } | ||
25 | |||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts new file mode 100644 index 000000000..519bdfab4 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts | |||
@@ -0,0 +1,99 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { User } from '@app/shared' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { Subject } from 'rxjs' | ||
5 | import { UserNotificationSetting, UserNotificationSettingValue, UserRight } from '../../../../../../shared' | ||
6 | import { Notifier, ServerService } from '@app/core' | ||
7 | import { debounce } from 'lodash-es' | ||
8 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-account-notification-preferences', | ||
12 | templateUrl: './my-account-notification-preferences.component.html', | ||
13 | styleUrls: [ './my-account-notification-preferences.component.scss' ] | ||
14 | }) | ||
15 | export class MyAccountNotificationPreferencesComponent implements OnInit { | ||
16 | @Input() user: User = null | ||
17 | @Input() userInformationLoaded: Subject<any> | ||
18 | |||
19 | notificationSettingKeys: (keyof UserNotificationSetting)[] = [] | ||
20 | emailNotifications: { [ id in keyof UserNotificationSetting ]: boolean } = {} as any | ||
21 | webNotifications: { [ id in keyof UserNotificationSetting ]: boolean } = {} as any | ||
22 | labelNotifications: { [ id in keyof UserNotificationSetting ]: string } = {} as any | ||
23 | rightNotifications: { [ id in keyof Partial<UserNotificationSetting> ]: UserRight } = {} as any | ||
24 | emailEnabled: boolean | ||
25 | |||
26 | private savePreferences = debounce(this.savePreferencesImpl.bind(this), 500) | ||
27 | |||
28 | constructor ( | ||
29 | private i18n: I18n, | ||
30 | private userNotificationService: UserNotificationService, | ||
31 | private serverService: ServerService, | ||
32 | private notifier: Notifier | ||
33 | ) { | ||
34 | this.labelNotifications = { | ||
35 | newVideoFromSubscription: this.i18n('New video from your subscriptions'), | ||
36 | newCommentOnMyVideo: this.i18n('New comment on your video'), | ||
37 | videoAbuseAsModerator: this.i18n('New video abuse on local video'), | ||
38 | blacklistOnMyVideo: this.i18n('One of your video is blacklisted/unblacklisted'), | ||
39 | myVideoPublished: this.i18n('Video published (after transcoding/scheduled update)'), | ||
40 | myVideoImportFinished: this.i18n('Video import finished'), | ||
41 | newUserRegistration: this.i18n('A new user registered on your instance'), | ||
42 | newFollow: this.i18n('You or your channel(s) has a new follower'), | ||
43 | commentMention: this.i18n('Someone mentioned you in video comments') | ||
44 | } | ||
45 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] | ||
46 | |||
47 | this.rightNotifications = { | ||
48 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, | ||
49 | newUserRegistration: UserRight.MANAGE_USERS | ||
50 | } | ||
51 | |||
52 | this.emailEnabled = this.serverService.getConfig().email.enabled | ||
53 | } | ||
54 | |||
55 | ngOnInit () { | ||
56 | this.userInformationLoaded.subscribe(() => this.loadNotificationSettings()) | ||
57 | } | ||
58 | |||
59 | hasUserRight (field: keyof UserNotificationSetting) { | ||
60 | const rightToHave = this.rightNotifications[field] | ||
61 | if (!rightToHave) return true // No rights needed | ||
62 | |||
63 | return this.user.hasRight(rightToHave) | ||
64 | } | ||
65 | |||
66 | updateEmailSetting (field: keyof UserNotificationSetting, value: boolean) { | ||
67 | if (value === true) this.user.notificationSettings[field] |= UserNotificationSettingValue.EMAIL | ||
68 | else this.user.notificationSettings[field] &= ~UserNotificationSettingValue.EMAIL | ||
69 | |||
70 | this.savePreferences() | ||
71 | } | ||
72 | |||
73 | updateWebSetting (field: keyof UserNotificationSetting, value: boolean) { | ||
74 | if (value === true) this.user.notificationSettings[field] |= UserNotificationSettingValue.WEB | ||
75 | else this.user.notificationSettings[field] &= ~UserNotificationSettingValue.WEB | ||
76 | |||
77 | this.savePreferences() | ||
78 | } | ||
79 | |||
80 | private savePreferencesImpl () { | ||
81 | this.userNotificationService.updateNotificationSettings(this.user, this.user.notificationSettings) | ||
82 | .subscribe( | ||
83 | () => { | ||
84 | this.notifier.success(this.i18n('Preferences saved'), undefined, 2000) | ||
85 | }, | ||
86 | |||
87 | err => this.notifier.error(err.message) | ||
88 | ) | ||
89 | } | ||
90 | |||
91 | private loadNotificationSettings () { | ||
92 | for (const key of Object.keys(this.user.notificationSettings)) { | ||
93 | const value = this.user.notificationSettings[key] | ||
94 | this.emailNotifications[key] = value & UserNotificationSettingValue.EMAIL | ||
95 | |||
96 | this.webNotifications[key] = value & UserNotificationSettingValue.WEB | ||
97 | } | ||
98 | } | ||
99 | } | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts index 967e21f0b..a9503ed1b 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, UserService } from '../../../shared' | 3 | import { FormReactive, UserService } from '../../../shared' |
4 | import { User } from '@app/shared' | 4 | import { User } from '@app/shared' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -21,7 +21,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit { | |||
21 | constructor ( | 21 | constructor ( |
22 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
23 | private userValidatorsService: UserValidatorsService, | 23 | private userValidatorsService: UserValidatorsService, |
24 | private notificationsService: NotificationsService, | 24 | private notifier: Notifier, |
25 | private userService: UserService, | 25 | private userService: UserService, |
26 | private i18n: I18n | 26 | private i18n: I18n |
27 | ) { | 27 | ) { |
@@ -53,7 +53,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit { | |||
53 | this.user.account.displayName = displayName | 53 | this.user.account.displayName = displayName |
54 | this.user.account.description = description | 54 | this.user.account.description = description |
55 | 55 | ||
56 | this.notificationsService.success(this.i18n('Success'), this.i18n('Profile updated.')) | 56 | this.notifier.success(this.i18n('Profile updated.')) |
57 | }, | 57 | }, |
58 | 58 | ||
59 | err => this.error = err.message | 59 | err => this.error = err.message |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html index c7e23cd1f..ad64f28fe 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html | |||
@@ -4,10 +4,11 @@ | |||
4 | <span i18n class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }} | 4 | <span i18n class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }} |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <ng-template [ngIf]="user && user.account"> | 7 | <div i18n class="account-title">Profile</div> |
8 | <div i18n class="account-title">Profile</div> | 8 | <my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile> |
9 | <my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile> | 9 | |
10 | </ng-template> | 10 | <div i18n class="account-title" id="notifications">Notifications</div> |
11 | <my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences> | ||
11 | 12 | ||
12 | <div i18n class="account-title">Password</div> | 13 | <div i18n class="account-title">Password</div> |
13 | <my-account-change-password></my-account-change-password> | 14 | <my-account-change-password></my-account-change-password> |
@@ -16,4 +17,4 @@ | |||
16 | <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> | 17 | <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> |
17 | 18 | ||
18 | <div i18n class="account-title">Danger zone</div> | 19 | <div i18n class="account-title">Danger zone</div> |
19 | <my-account-danger-zone [user]="user"></my-account-danger-zone> \ No newline at end of file | 20 | <my-account-danger-zone [user]="user"></my-account-danger-zone> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index 62053d97b..f4b954e54 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { BytesPipe } from 'ngx-pipes' | 3 | import { BytesPipe } from 'ngx-pipes' |
4 | import { AuthService } from '../../core' | 4 | import { AuthService } from '../../core' |
5 | import { User } from '../../shared' | 5 | import { User } from '../../shared' |
@@ -19,7 +19,7 @@ export class MyAccountSettingsComponent implements OnInit { | |||
19 | constructor ( | 19 | constructor ( |
20 | private userService: UserService, | 20 | private userService: UserService, |
21 | private authService: AuthService, | 21 | private authService: AuthService, |
22 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
23 | private i18n: I18n | 23 | private i18n: I18n |
24 | ) {} | 24 | ) {} |
25 | 25 | ||
@@ -48,12 +48,12 @@ export class MyAccountSettingsComponent implements OnInit { | |||
48 | this.userService.changeAvatar(formData) | 48 | this.userService.changeAvatar(formData) |
49 | .subscribe( | 49 | .subscribe( |
50 | data => { | 50 | data => { |
51 | this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) | 51 | this.notifier.success(this.i18n('Avatar changed.')) |
52 | 52 | ||
53 | this.user.updateAccountAvatar(data.avatar) | 53 | this.user.updateAccountAvatar(data.avatar) |
54 | }, | 54 | }, |
55 | 55 | ||
56 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 56 | err => this.notifier.error(err.message) |
57 | ) | 57 | ) |
58 | } | 58 | } |
59 | } | 59 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts index 6c9a7ce75..b8f80bc1a 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { UserUpdateMe } from '../../../../../../shared' | 3 | import { UserUpdateMe } from '../../../../../../shared' |
4 | import { AuthService } from '../../../core' | 4 | import { AuthService } from '../../../core' |
5 | import { FormReactive, User, UserService } from '../../../shared' | 5 | import { FormReactive, User, UserService } from '../../../shared' |
@@ -19,7 +19,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
19 | constructor ( | 19 | constructor ( |
20 | protected formValidatorService: FormValidatorService, | 20 | protected formValidatorService: FormValidatorService, |
21 | private authService: AuthService, | 21 | private authService: AuthService, |
22 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
23 | private userService: UserService, | 23 | private userService: UserService, |
24 | private i18n: I18n | 24 | private i18n: I18n |
25 | ) { | 25 | ) { |
@@ -54,12 +54,12 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
54 | 54 | ||
55 | this.userService.updateMyProfile(details).subscribe( | 55 | this.userService.updateMyProfile(details).subscribe( |
56 | () => { | 56 | () => { |
57 | this.notificationsService.success(this.i18n('Success'), this.i18n('Information updated.')) | 57 | this.notifier.success(this.i18n('Information updated.')) |
58 | 58 | ||
59 | this.authService.refreshUserInformation() | 59 | this.authService.refreshUserInformation() |
60 | }, | 60 | }, |
61 | 61 | ||
62 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 62 | err => this.notifier.error(err.message) |
63 | ) | 63 | ) |
64 | } | 64 | } |
65 | } | 65 | } |
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts index 9517a3705..9d2dccdf0 100644 --- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { UserSubscriptionService } from '@app/shared/user-subscription' | 5 | import { UserSubscriptionService } from '@app/shared/user-subscription' |
@@ -21,7 +21,7 @@ export class MyAccountSubscriptionsComponent implements OnInit { | |||
21 | 21 | ||
22 | constructor ( | 22 | constructor ( |
23 | private userSubscriptionService: UserSubscriptionService, | 23 | private userSubscriptionService: UserSubscriptionService, |
24 | private notificationsService: NotificationsService, | 24 | private notifier: Notifier, |
25 | private i18n: I18n | 25 | private i18n: I18n |
26 | ) {} | 26 | ) {} |
27 | 27 | ||
@@ -37,7 +37,7 @@ export class MyAccountSubscriptionsComponent implements OnInit { | |||
37 | this.pagination.totalItems = res.total | 37 | this.pagination.totalItems = res.total |
38 | }, | 38 | }, |
39 | 39 | ||
40 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 40 | error => this.notifier.error(error.message) |
41 | ) | 41 | ) |
42 | } | 42 | } |
43 | 43 | ||
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts index 81608d837..a68f79b47 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { AuthService, Notifier } from '@app/core' |
4 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' | 4 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' |
5 | import { VideoChannelCreate } from '../../../../../shared/models/videos' | 5 | import { VideoChannelCreate } from '../../../../../shared/models/videos' |
6 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 6 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
7 | import { AuthService } from '@app/core' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
9 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
10 | import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' | 9 | import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' |
@@ -21,7 +20,7 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE | |||
21 | protected formValidatorService: FormValidatorService, | 20 | protected formValidatorService: FormValidatorService, |
22 | private authService: AuthService, | 21 | private authService: AuthService, |
23 | private videoChannelValidatorsService: VideoChannelValidatorsService, | 22 | private videoChannelValidatorsService: VideoChannelValidatorsService, |
24 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
25 | private router: Router, | 24 | private router: Router, |
26 | private videoChannelService: VideoChannelService, | 25 | private videoChannelService: VideoChannelService, |
27 | private i18n: I18n | 26 | private i18n: I18n |
@@ -56,8 +55,8 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE | |||
56 | this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe( | 55 | this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe( |
57 | () => { | 56 | () => { |
58 | this.authService.refreshUserInformation() | 57 | this.authService.refreshUserInformation() |
59 | this.notificationsService.success( | 58 | |
60 | this.i18n('Success'), | 59 | this.notifier.success( |
61 | this.i18n('Video channel {{videoChannelName}} created.', { videoChannelName: videoChannelCreate.displayName }) | 60 | this.i18n('Video channel {{videoChannelName}} created.', { videoChannelName: videoChannelCreate.displayName }) |
62 | ) | 61 | ) |
63 | this.router.navigate([ '/my-account', 'video-channels' ]) | 62 | this.router.navigate([ '/my-account', 'video-channels' ]) |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts index 5d43956f2..da4fb645a 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { AuthService, Notifier, ServerService } from '@app/core' |
4 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' | 4 | import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' |
5 | import { VideoChannelUpdate } from '../../../../../shared/models/videos' | 5 | import { VideoChannelUpdate } from '../../../../../shared/models/videos' |
6 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 6 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
7 | import { Subscription } from 'rxjs' | 7 | import { Subscription } from 'rxjs' |
8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
9 | import { AuthService, ServerService } from '@app/core' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 10 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
12 | import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' | 11 | import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' |
@@ -26,7 +25,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
26 | protected formValidatorService: FormValidatorService, | 25 | protected formValidatorService: FormValidatorService, |
27 | private authService: AuthService, | 26 | private authService: AuthService, |
28 | private videoChannelValidatorsService: VideoChannelValidatorsService, | 27 | private videoChannelValidatorsService: VideoChannelValidatorsService, |
29 | private notificationsService: NotificationsService, | 28 | private notifier: Notifier, |
30 | private router: Router, | 29 | private router: Router, |
31 | private route: ActivatedRoute, | 30 | private route: ActivatedRoute, |
32 | private videoChannelService: VideoChannelService, | 31 | private videoChannelService: VideoChannelService, |
@@ -79,10 +78,11 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
79 | this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe( | 78 | this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe( |
80 | () => { | 79 | () => { |
81 | this.authService.refreshUserInformation() | 80 | this.authService.refreshUserInformation() |
82 | this.notificationsService.success( | 81 | |
83 | this.i18n('Success'), | 82 | this.notifier.success( |
84 | this.i18n('Video channel {{videoChannelName}} updated.', { videoChannelName: videoChannelUpdate.displayName }) | 83 | this.i18n('Video channel {{videoChannelName}} updated.', { videoChannelName: videoChannelUpdate.displayName }) |
85 | ) | 84 | ) |
85 | |||
86 | this.router.navigate([ '/my-account', 'video-channels' ]) | 86 | this.router.navigate([ '/my-account', 'video-channels' ]) |
87 | }, | 87 | }, |
88 | 88 | ||
@@ -94,12 +94,12 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
94 | this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData) | 94 | this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData) |
95 | .subscribe( | 95 | .subscribe( |
96 | data => { | 96 | data => { |
97 | this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) | 97 | this.notifier.success(this.i18n('Avatar changed.')) |
98 | 98 | ||
99 | this.videoChannelToUpdate.updateAvatar(data.avatar) | 99 | this.videoChannelToUpdate.updateAvatar(data.avatar) |
100 | }, | 100 | }, |
101 | 101 | ||
102 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 102 | err => this.notifier.error(err.message) |
103 | ) | 103 | ) |
104 | } | 104 | } |
105 | 105 | ||
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html index df74b19b6..51db2e75d 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="video-channels-header"> | 1 | <div class="video-channels-header"> |
2 | <a class="create-button" routerLink="create"> | 2 | <a class="create-button" routerLink="create"> |
3 | <span class="icon icon-add"></span> | 3 | <my-global-icon iconName="add"></my-global-icon> |
4 | <ng-container i18n>Create another video channel</ng-container> | 4 | <ng-container i18n>Create another video channel</ng-container> |
5 | </a> | 5 | </a> |
6 | </div> | 6 | </div> |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss index 472cbb723..77fce138b 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss | |||
@@ -2,7 +2,7 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .create-button { | 4 | .create-button { |
5 | @include create-button('../../../assets/images/global/add.svg'); | 5 | @include create-button; |
6 | } | 6 | } |
7 | 7 | ||
8 | /deep/ .action-button { | 8 | /deep/ .action-button { |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts index 6d1098865..da2c5bcd3 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { AuthService } from '../../core/auth' | 3 | import { AuthService } from '../../core/auth' |
4 | import { ConfirmService } from '../../core/confirm' | 4 | import { ConfirmService } from '../../core/confirm' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
@@ -20,7 +20,7 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
20 | 20 | ||
21 | constructor ( | 21 | constructor ( |
22 | private authService: AuthService, | 22 | private authService: AuthService, |
23 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
24 | private confirmService: ConfirmService, | 24 | private confirmService: ConfirmService, |
25 | private videoChannelService: VideoChannelService, | 25 | private videoChannelService: VideoChannelService, |
26 | private i18n: I18n | 26 | private i18n: I18n |
@@ -35,10 +35,14 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
35 | async deleteVideoChannel (videoChannel: VideoChannel) { | 35 | async deleteVideoChannel (videoChannel: VideoChannel) { |
36 | const res = await this.confirmService.confirmWithInput( | 36 | const res = await this.confirmService.confirmWithInput( |
37 | this.i18n( | 37 | this.i18n( |
38 | 'Do you really want to delete {{videoChannelName}}? It will delete all videos uploaded in this channel too.', | 38 | 'Do you really want to delete {{channelDisplayName}}? It will delete all videos uploaded in this channel, ' + |
39 | { videoChannelName: videoChannel.displayName } | 39 | 'and you will not be able to create another channel with the same name ({{channelName}})!', |
40 | { channelDisplayName: videoChannel.displayName, channelName: videoChannel.name } | ||
41 | ), | ||
42 | this.i18n( | ||
43 | 'Please type the display name of the video channel ({{displayName}}) to confirm', | ||
44 | { displayName: videoChannel.displayName } | ||
40 | ), | 45 | ), |
41 | this.i18n('Please type the name of the video channel to confirm'), | ||
42 | videoChannel.displayName, | 46 | videoChannel.displayName, |
43 | this.i18n('Delete') | 47 | this.i18n('Delete') |
44 | ) | 48 | ) |
@@ -46,15 +50,14 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
46 | 50 | ||
47 | this.videoChannelService.removeVideoChannel(videoChannel) | 51 | this.videoChannelService.removeVideoChannel(videoChannel) |
48 | .subscribe( | 52 | .subscribe( |
49 | status => { | 53 | () => { |
50 | this.loadVideoChannels() | 54 | this.loadVideoChannels() |
51 | this.notificationsService.success( | 55 | this.notifier.success( |
52 | this.i18n('Success'), | ||
53 | this.i18n('Video channel {{videoChannelName}} deleted.', { videoChannelName: videoChannel.displayName }) | 56 | this.i18n('Video channel {{videoChannelName}} deleted.', { videoChannelName: videoChannel.displayName }) |
54 | ) | 57 | ) |
55 | }, | 58 | }, |
56 | 59 | ||
57 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 60 | error => this.notifier.error(error.message) |
58 | ) | 61 | ) |
59 | } | 62 | } |
60 | 63 | ||
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts index 5b920c98d..21a10c8ff 100644 --- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts +++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { RestPagination, RestTable } from '@app/shared' | 2 | import { RestPagination, RestTable } from '@app/shared' |
3 | import { SortMeta } from 'primeng/components/common/sortmeta' | 3 | import { SortMeta } from 'primeng/components/common/sortmeta' |
4 | import { NotificationsService } from 'angular2-notifications' | 4 | import { Notifier } from '@app/core' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { VideoImport, VideoImportState } from '../../../../../shared/models/videos' | 6 | import { VideoImport, VideoImportState } from '../../../../../shared/models/videos' |
7 | import { VideoImportService } from '@app/shared/video-import' | 7 | import { VideoImportService } from '@app/shared/video-import' |
@@ -19,7 +19,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit | |||
19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
20 | 20 | ||
21 | constructor ( | 21 | constructor ( |
22 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
23 | private videoImportService: VideoImportService, | 23 | private videoImportService: VideoImportService, |
24 | private i18n: I18n | 24 | private i18n: I18n |
25 | ) { | 25 | ) { |
@@ -58,7 +58,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit | |||
58 | this.totalRecords = resultList.total | 58 | this.totalRecords = resultList.total |
59 | }, | 59 | }, |
60 | 60 | ||
61 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 61 | err => this.notifier.error(err.message) |
62 | ) | 62 | ) |
63 | } | 63 | } |
64 | } | 64 | } |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html index a6911e4bf..69748ef37 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html | |||
@@ -32,7 +32,7 @@ | |||
32 | </span> | 32 | </span> |
33 | 33 | ||
34 | <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()"> | 34 | <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()"> |
35 | <span class="icon icon-delete-white"></span> | 35 | <my-global-icon iconName="delete"></my-global-icon> |
36 | <ng-container i18n>Delete</ng-container> | 36 | <ng-container i18n>Delete</ng-container> |
37 | </span> | 37 | </span> |
38 | </div> | 38 | </div> |
@@ -45,7 +45,7 @@ | |||
45 | 45 | ||
46 | <my-button i18n-label label="Change ownership" | 46 | <my-button i18n-label label="Change ownership" |
47 | className="action-button-change-ownership" | 47 | className="action-button-change-ownership" |
48 | icon="icon-im-with-her" | 48 | icon="im-with-her" |
49 | (click)="changeOwnership($event, video)" | 49 | (click)="changeOwnership($event, video)" |
50 | ></my-button> | 50 | ></my-button> |
51 | </div> | 51 | </div> |
@@ -53,4 +53,4 @@ | |||
53 | </div> | 53 | </div> |
54 | </div> | 54 | </div> |
55 | 55 | ||
56 | <my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership> \ No newline at end of file | 56 | <my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership> |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss index 2db81a3fe..39d0cf2f7 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss | |||
@@ -23,14 +23,11 @@ | |||
23 | .action-button-delete-selection { | 23 | .action-button-delete-selection { |
24 | @include peertube-button; | 24 | @include peertube-button; |
25 | @include orange-button; | 25 | @include orange-button; |
26 | } | 26 | @include button-with-icon(21px); |
27 | |||
28 | .icon.icon-delete-white { | ||
29 | @include icon(21px); | ||
30 | 27 | ||
31 | position: relative; | 28 | my-global-icon { |
32 | top: -2px; | 29 | @include apply-svg-color(#fff); |
33 | background-image: url('../../../assets/images/global/delete-white.svg'); | 30 | } |
34 | } | 31 | } |
35 | } | 32 | } |
36 | } | 33 | } |
@@ -97,7 +94,7 @@ | |||
97 | } | 94 | } |
98 | } | 95 | } |
99 | 96 | ||
100 | @media screen and (max-width: 800px) { | 97 | @media screen and (max-width: $small-view) { |
101 | .video { | 98 | .video { |
102 | flex-direction: column; | 99 | flex-direction: column; |
103 | height: auto; | 100 | height: auto; |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts index 2d88ac760..41608f796 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts | |||
@@ -5,7 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router' | |||
5 | import { Location } from '@angular/common' | 5 | import { Location } from '@angular/common' |
6 | import { immutableAssign } from '@app/shared/misc/utils' | 6 | import { immutableAssign } from '@app/shared/misc/utils' |
7 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | 7 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { Notifier } from '@app/core' |
9 | import { AuthService } from '../../core/auth' | 9 | import { AuthService } from '../../core/auth' |
10 | import { ConfirmService } from '../../core/confirm' | 10 | import { ConfirmService } from '../../core/confirm' |
11 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 11 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
@@ -40,7 +40,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni | |||
40 | protected router: Router, | 40 | protected router: Router, |
41 | protected route: ActivatedRoute, | 41 | protected route: ActivatedRoute, |
42 | protected authService: AuthService, | 42 | protected authService: AuthService, |
43 | protected notificationsService: NotificationsService, | 43 | protected notifier: Notifier, |
44 | protected location: Location, | 44 | protected location: Location, |
45 | protected screenService: ScreenService, | 45 | protected screenService: ScreenService, |
46 | protected i18n: I18n, | 46 | protected i18n: I18n, |
@@ -102,16 +102,13 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni | |||
102 | .pipe(concatAll()) | 102 | .pipe(concatAll()) |
103 | .subscribe( | 103 | .subscribe( |
104 | res => { | 104 | res => { |
105 | this.notificationsService.success( | 105 | this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })) |
106 | this.i18n('Success'), | ||
107 | this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }) | ||
108 | ) | ||
109 | 106 | ||
110 | this.abortSelectionMode() | 107 | this.abortSelectionMode() |
111 | this.reloadVideos() | 108 | this.reloadVideos() |
112 | }, | 109 | }, |
113 | 110 | ||
114 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 111 | err => this.notifier.error(err.message) |
115 | ) | 112 | ) |
116 | } | 113 | } |
117 | 114 | ||
@@ -124,15 +121,12 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni | |||
124 | 121 | ||
125 | this.videoService.removeVideo(video.id) | 122 | this.videoService.removeVideo(video.id) |
126 | .subscribe( | 123 | .subscribe( |
127 | status => { | 124 | () => { |
128 | this.notificationsService.success( | 125 | this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name })) |
129 | this.i18n('Success'), | ||
130 | this.i18n('Video {{videoName}} deleted.', { videoName: video.name }) | ||
131 | ) | ||
132 | this.reloadVideos() | 126 | this.reloadVideos() |
133 | }, | 127 | }, |
134 | 128 | ||
135 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 129 | error => this.notifier.error(error.message) |
136 | ) | 130 | ) |
137 | } | 131 | } |
138 | 132 | ||
diff --git a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html index 7c0df850d..22f127904 100644 --- a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html +++ b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html | |||
@@ -1,7 +1,8 @@ | |||
1 | <ng-template #modal let-close="close" let-dismiss="dismiss"> | 1 | <ng-template #modal let-close="close" let-dismiss="dismiss"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Change ownership</h4> | 3 | <h4 i18n class="modal-title">Change ownership</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span> | 4 | |
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon> | ||
5 | </div> | 6 | </div> |
6 | 7 | ||
7 | <div class="modal-body" [formGroup]="form"> | 8 | <div class="modal-body" [formGroup]="form"> |
diff --git a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts index 9f94f3c13..37d7cf2a4 100644 --- a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts +++ b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { FormReactive, UserService } from '../../../shared/index' | 4 | import { FormReactive, UserService } from '../../../shared/index' |
5 | import { Video } from '@app/shared/video/video.model' | 5 | import { Video } from '@app/shared/video/video.model' |
@@ -25,7 +25,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni | |||
25 | protected formValidatorService: FormValidatorService, | 25 | protected formValidatorService: FormValidatorService, |
26 | private videoChangeOwnershipValidatorsService: VideoChangeOwnershipValidatorsService, | 26 | private videoChangeOwnershipValidatorsService: VideoChangeOwnershipValidatorsService, |
27 | private videoOwnershipService: VideoOwnershipService, | 27 | private videoOwnershipService: VideoOwnershipService, |
28 | private notificationsService: NotificationsService, | 28 | private notifier: Notifier, |
29 | private userService: UserService, | 29 | private userService: UserService, |
30 | private modalService: NgbModal, | 30 | private modalService: NgbModal, |
31 | private i18n: I18n | 31 | private i18n: I18n |
@@ -53,11 +53,9 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni | |||
53 | const query = event.query | 53 | const query = event.query |
54 | this.userService.autocomplete(query) | 54 | this.userService.autocomplete(query) |
55 | .subscribe( | 55 | .subscribe( |
56 | usernames => { | 56 | usernames => this.usernamePropositions = usernames, |
57 | this.usernamePropositions = usernames | ||
58 | }, | ||
59 | 57 | ||
60 | err => this.notificationsService.error('Error', err.message) | 58 | err => this.notifier.error(err.message) |
61 | ) | 59 | ) |
62 | } | 60 | } |
63 | 61 | ||
@@ -67,9 +65,9 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni | |||
67 | this.videoOwnershipService | 65 | this.videoOwnershipService |
68 | .changeOwnership(this.video.id, username) | 66 | .changeOwnership(this.video.id, username) |
69 | .subscribe( | 67 | .subscribe( |
70 | () => this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership change request sent.')), | 68 | () => this.notifier.success(this.i18n('Ownership change request sent.')), |
71 | 69 | ||
72 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 70 | err => this.notifier.error(err.message) |
73 | ) | 71 | ) |
74 | } | 72 | } |
75 | } | 73 | } |
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html index 41333c25a..3999252be 100644 --- a/client/src/app/+my-account/my-account.component.html +++ b/client/src/app/+my-account/my-account.component.html | |||
@@ -1,40 +1,5 @@ | |||
1 | <div class="row"> | 1 | <div class="row"> |
2 | <div class="sub-menu"> | 2 | <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown> |
3 | <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a> | ||
4 | |||
5 | <div ngbDropdown class="my-library"> | ||
6 | <span role="button" class="title-page" [ngClass]="{ active: libraryLabel !== '' }" ngbDropdownToggle> | ||
7 | <ng-container i18n>My library</ng-container> | ||
8 | <ng-container *ngIf="libraryLabel"> - {{ libraryLabel }}</ng-container> | ||
9 | </span> | ||
10 | |||
11 | <div ngbDropdownMenu> | ||
12 | <a class="dropdown-item" i18n routerLink="/my-account/video-channels">My channels</a> | ||
13 | |||
14 | <a class="dropdown-item" i18n routerLink="/my-account/videos">My videos</a> | ||
15 | |||
16 | <a class="dropdown-item" i18n routerLink="/my-account/subscriptions">My subscriptions</a> | ||
17 | |||
18 | <a class="dropdown-item" *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports">My imports</a> | ||
19 | </div> | ||
20 | </div> | ||
21 | |||
22 | <div ngbDropdown class="misc"> | ||
23 | <span role="button" class="title-page" [ngClass]="{ active: miscLabel !== '' }" ngbDropdownToggle> | ||
24 | <ng-container i18n>Misc</ng-container> | ||
25 | <ng-container *ngIf="miscLabel"> - {{ miscLabel }}</ng-container> | ||
26 | </span> | ||
27 | |||
28 | <div ngbDropdownMenu> | ||
29 | <a class="dropdown-item" i18n routerLink="/my-account/blocklist/accounts">Muted accounts</a> | ||
30 | |||
31 | <a class="dropdown-item" i18n routerLink="/my-account/blocklist/servers">Muted instances</a> | ||
32 | |||
33 | <a class="dropdown-item" i18n routerLink="/my-account/ownership">Ownership changes</a> | ||
34 | </div> | ||
35 | </div> | ||
36 | |||
37 | </div> | ||
38 | 3 | ||
39 | <div class="margin-content"> | 4 | <div class="margin-content"> |
40 | <router-outlet></router-outlet> | 5 | <router-outlet></router-outlet> |
diff --git a/client/src/app/+my-account/my-account.component.scss b/client/src/app/+my-account/my-account.component.scss index 6243c6dcf..4f111efdf 100644 --- a/client/src/app/+my-account/my-account.component.scss +++ b/client/src/app/+my-account/my-account.component.scss | |||
@@ -1,14 +1,3 @@ | |||
1 | .my-library, .misc { | 1 | .row { |
2 | span[role=button] { | 2 | flex-direction: column; |
3 | cursor: pointer; | ||
4 | } | ||
5 | |||
6 | a { | ||
7 | display: block; | ||
8 | } | ||
9 | } | 3 | } |
10 | |||
11 | /deep/ .dropdown-toggle::after { | ||
12 | position: relative; | ||
13 | top: 2px; | ||
14 | } \ No newline at end of file | ||
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index d728caf07..8a4102d80 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts | |||
@@ -1,38 +1,80 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { NavigationStart, Router } from '@angular/router' | ||
4 | import { filter } from 'rxjs/operators' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { Subscription } from 'rxjs' | 4 | import { TopMenuDropdownParam } from '@app/shared/menu/top-menu-dropdown.component' |
7 | 5 | ||
8 | @Component({ | 6 | @Component({ |
9 | selector: 'my-my-account', | 7 | selector: 'my-my-account', |
10 | templateUrl: './my-account.component.html', | 8 | templateUrl: './my-account.component.html', |
11 | styleUrls: [ './my-account.component.scss' ] | 9 | styleUrls: [ './my-account.component.scss' ] |
12 | }) | 10 | }) |
13 | export class MyAccountComponent implements OnInit, OnDestroy { | 11 | export class MyAccountComponent { |
14 | 12 | menuEntries: TopMenuDropdownParam[] = [] | |
15 | libraryLabel = '' | ||
16 | miscLabel = '' | ||
17 | |||
18 | private routeSub: Subscription | ||
19 | 13 | ||
20 | constructor ( | 14 | constructor ( |
21 | private serverService: ServerService, | 15 | private serverService: ServerService, |
22 | private router: Router, | ||
23 | private i18n: I18n | 16 | private i18n: I18n |
24 | ) {} | 17 | ) { |
18 | |||
19 | const libraryEntries: TopMenuDropdownParam = { | ||
20 | label: this.i18n('My library'), | ||
21 | children: [ | ||
22 | { | ||
23 | label: this.i18n('My channels'), | ||
24 | routerLink: '/my-account/video-channels' | ||
25 | }, | ||
26 | { | ||
27 | label: this.i18n('My videos'), | ||
28 | routerLink: '/my-account/videos' | ||
29 | }, | ||
30 | { | ||
31 | label: this.i18n('My subscriptions'), | ||
32 | routerLink: '/my-account/subscriptions' | ||
33 | }, | ||
34 | { | ||
35 | label: this.i18n('My history'), | ||
36 | routerLink: '/my-account/history/videos' | ||
37 | } | ||
38 | ] | ||
39 | } | ||
25 | 40 | ||
26 | ngOnInit () { | 41 | if (this.isVideoImportEnabled()) { |
27 | this.updateLabels(this.router.url) | 42 | libraryEntries.children.push({ |
43 | label: 'My imports', | ||
44 | routerLink: '/my-account/video-imports' | ||
45 | }) | ||
46 | } | ||
28 | 47 | ||
29 | this.routeSub = this.router.events | 48 | const miscEntries: TopMenuDropdownParam = { |
30 | .pipe(filter(event => event instanceof NavigationStart)) | 49 | label: this.i18n('Misc'), |
31 | .subscribe((event: NavigationStart) => this.updateLabels(event.url)) | 50 | children: [ |
32 | } | 51 | { |
52 | label: this.i18n('Muted accounts'), | ||
53 | routerLink: '/my-account/blocklist/accounts' | ||
54 | }, | ||
55 | { | ||
56 | label: this.i18n('Muted instances'), | ||
57 | routerLink: '/my-account/blocklist/servers' | ||
58 | }, | ||
59 | { | ||
60 | label: this.i18n('Ownership changes'), | ||
61 | routerLink: '/my-account/ownership' | ||
62 | } | ||
63 | ] | ||
64 | } | ||
33 | 65 | ||
34 | ngOnDestroy () { | 66 | this.menuEntries = [ |
35 | if (this.routeSub) this.routeSub.unsubscribe() | 67 | { |
68 | label: this.i18n('My settings'), | ||
69 | routerLink: '/my-account/settings' | ||
70 | }, | ||
71 | { | ||
72 | label: this.i18n('My notifications'), | ||
73 | routerLink: '/my-account/notifications' | ||
74 | }, | ||
75 | libraryEntries, | ||
76 | miscEntries | ||
77 | ] | ||
36 | } | 78 | } |
37 | 79 | ||
38 | isVideoImportEnabled () { | 80 | isVideoImportEnabled () { |
@@ -41,27 +83,4 @@ export class MyAccountComponent implements OnInit, OnDestroy { | |||
41 | return importConfig.http.enabled || importConfig.torrent.enabled | 83 | return importConfig.http.enabled || importConfig.torrent.enabled |
42 | } | 84 | } |
43 | 85 | ||
44 | private updateLabels (url: string) { | ||
45 | const [ path ] = url.split('?') | ||
46 | |||
47 | if (path.startsWith('/my-account/video-channels')) { | ||
48 | this.libraryLabel = this.i18n('Channels') | ||
49 | } else if (path.startsWith('/my-account/videos')) { | ||
50 | this.libraryLabel = this.i18n('Videos') | ||
51 | } else if (path.startsWith('/my-account/subscriptions')) { | ||
52 | this.libraryLabel = this.i18n('Subscriptions') | ||
53 | } else if (path.startsWith('/my-account/video-imports')) { | ||
54 | this.libraryLabel = this.i18n('Video imports') | ||
55 | } else { | ||
56 | this.libraryLabel = '' | ||
57 | } | ||
58 | |||
59 | if (path.startsWith('/my-account/blocklist/accounts')) { | ||
60 | this.miscLabel = this.i18n('Muted accounts') | ||
61 | } else if (path.startsWith('/my-account/blocklist/servers')) { | ||
62 | this.miscLabel = this.i18n('Muted instances') | ||
63 | } else { | ||
64 | this.miscLabel = '' | ||
65 | } | ||
66 | } | ||
67 | } | 86 | } |
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 017ebd57d..18f51f171 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { TableModule } from 'primeng/table' | 1 | import { TableModule } from 'primeng/table' |
2 | import { NgModule } from '@angular/core' | 2 | import { NgModule } from '@angular/core' |
3 | import { AutoCompleteModule } from 'primeng/autocomplete' | 3 | import { AutoCompleteModule } from 'primeng/autocomplete' |
4 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
4 | import { SharedModule } from '../shared' | 5 | import { SharedModule } from '../shared' |
5 | import { MyAccountRoutingModule } from './my-account-routing.module' | 6 | import { MyAccountRoutingModule } from './my-account-routing.module' |
6 | import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' | 7 | import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' |
@@ -21,6 +22,9 @@ import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settin | |||
21 | import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component' | 22 | import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component' |
22 | import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component' | 23 | import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component' |
23 | import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' | 24 | import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' |
25 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' | ||
26 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' | ||
27 | import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences' | ||
24 | 28 | ||
25 | @NgModule({ | 29 | @NgModule({ |
26 | imports: [ | 30 | imports: [ |
@@ -28,7 +32,8 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b | |||
28 | MyAccountRoutingModule, | 32 | MyAccountRoutingModule, |
29 | AutoCompleteModule, | 33 | AutoCompleteModule, |
30 | SharedModule, | 34 | SharedModule, |
31 | TableModule | 35 | TableModule, |
36 | InputSwitchModule | ||
32 | ], | 37 | ], |
33 | 38 | ||
34 | declarations: [ | 39 | declarations: [ |
@@ -49,7 +54,10 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b | |||
49 | MyAccountDangerZoneComponent, | 54 | MyAccountDangerZoneComponent, |
50 | MyAccountSubscriptionsComponent, | 55 | MyAccountSubscriptionsComponent, |
51 | MyAccountBlocklistComponent, | 56 | MyAccountBlocklistComponent, |
52 | MyAccountServerBlocklistComponent | 57 | MyAccountServerBlocklistComponent, |
58 | MyAccountHistoryComponent, | ||
59 | MyAccountNotificationsComponent, | ||
60 | MyAccountNotificationPreferencesComponent | ||
53 | ], | 61 | ], |
54 | 62 | ||
55 | exports: [ | 63 | exports: [ |
diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.ts b/client/src/app/+my-account/shared/actor-avatar-info.component.ts index 54bacc212..72c815a0c 100644 --- a/client/src/app/+my-account/shared/actor-avatar-info.component.ts +++ b/client/src/app/+my-account/shared/actor-avatar-info.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core' |
2 | import { ServerService } from '../../core/server' | 2 | import { ServerService } from '../../core/server' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
5 | import { Account } from '@app/shared/account/account.model' | 4 | import { Account } from '@app/shared/account/account.model' |
5 | import { Notifier } from '@app/core' | ||
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-actor-avatar-info', | 8 | selector: 'my-actor-avatar-info', |
@@ -18,13 +18,13 @@ export class ActorAvatarInfoComponent { | |||
18 | 18 | ||
19 | constructor ( | 19 | constructor ( |
20 | private serverService: ServerService, | 20 | private serverService: ServerService, |
21 | private notificationsService: NotificationsService | 21 | private notifier: Notifier |
22 | ) {} | 22 | ) {} |
23 | 23 | ||
24 | onAvatarChange () { | 24 | onAvatarChange () { |
25 | const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] | 25 | const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] |
26 | if (avatarfile.size > this.maxAvatarSize) { | 26 | if (avatarfile.size > this.maxAvatarSize) { |
27 | this.notificationsService.error('Error', 'This image is too large.') | 27 | this.notifier.error('Error', 'This image is too large.') |
28 | return | 28 | return |
29 | } | 29 | } |
30 | 30 | ||
diff --git a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts index 995f42ffc..cfd471fa4 100644 --- a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts +++ b/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier, RedirectService } from '@app/core' |
4 | import { ServerService } from '@app/core/server' | 4 | import { ServerService } from '@app/core/server' |
5 | import { RedirectService } from '@app/core' | 5 | import { FormReactive, UserService } from '@app/shared' |
6 | import { UserService, FormReactive } from '@app/shared' | ||
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' | 7 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' |
9 | 8 | ||
@@ -20,7 +19,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements | |||
20 | private userValidatorsService: UserValidatorsService, | 19 | private userValidatorsService: UserValidatorsService, |
21 | private userService: UserService, | 20 | private userService: UserService, |
22 | private serverService: ServerService, | 21 | private serverService: ServerService, |
23 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
24 | private redirectService: RedirectService, | 23 | private redirectService: RedirectService, |
25 | private i18n: I18n | 24 | private i18n: I18n |
26 | ) { | 25 | ) { |
@@ -46,12 +45,12 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements | |||
46 | 'An email with verification link will be sent to {{email}}.', | 45 | 'An email with verification link will be sent to {{email}}.', |
47 | { email } | 46 | { email } |
48 | ) | 47 | ) |
49 | this.notificationsService.success(this.i18n('Success'), message) | 48 | this.notifier.success(message) |
50 | this.redirectService.redirectToHomepage() | 49 | this.redirectService.redirectToHomepage() |
51 | }, | 50 | }, |
52 | 51 | ||
53 | err => { | 52 | err => { |
54 | this.notificationsService.error(this.i18n('Error'), err.message) | 53 | this.notifier.error(err.message) |
55 | } | 54 | } |
56 | ) | 55 | ) |
57 | } | 56 | } |
diff --git a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html index 30ace5e10..a83d4a3c2 100644 --- a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html +++ b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <ng-template #verificationError> | 9 | <ng-template #verificationError> |
10 | <div> | 10 | <div> |
11 | <span i18n>An error occurred. </span> | 11 | <span i18n>An error occurred. </span> |
12 | <a i18n routerLink="/verify-account/ask-email">Request new verification email.</a> | 12 | <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a> |
13 | </div> | 13 | </div> |
14 | </ng-template> | 14 | </ng-template> |
15 | </div> | 15 | </div> |
diff --git a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts index e4a5522c8..f9ecf664b 100644 --- a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts +++ b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { NotificationsService } from 'angular2-notifications' | 4 | import { Notifier } from '@app/core' |
5 | import { UserService } from '@app/shared' | 5 | import { UserService } from '@app/shared' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
@@ -17,7 +17,7 @@ export class VerifyAccountEmailComponent implements OnInit { | |||
17 | 17 | ||
18 | constructor ( | 18 | constructor ( |
19 | private userService: UserService, | 19 | private userService: UserService, |
20 | private notificationsService: NotificationsService, | 20 | private notifier: Notifier, |
21 | private router: Router, | 21 | private router: Router, |
22 | private route: ActivatedRoute, | 22 | private route: ActivatedRoute, |
23 | private i18n: I18n | 23 | private i18n: I18n |
@@ -29,7 +29,7 @@ export class VerifyAccountEmailComponent implements OnInit { | |||
29 | this.verificationString = this.route.snapshot.queryParams['verificationString'] | 29 | this.verificationString = this.route.snapshot.queryParams['verificationString'] |
30 | 30 | ||
31 | if (!this.userId || !this.verificationString) { | 31 | if (!this.userId || !this.verificationString) { |
32 | this.notificationsService.error(this.i18n('Error'), this.i18n('Unable to find user id or verification string.')) | 32 | this.notifier.error(this.i18n('Unable to find user id or verification string.')) |
33 | } else { | 33 | } else { |
34 | this.verifyEmail() | 34 | this.verifyEmail() |
35 | } | 35 | } |
@@ -46,7 +46,7 @@ export class VerifyAccountEmailComponent implements OnInit { | |||
46 | }, | 46 | }, |
47 | 47 | ||
48 | err => { | 48 | err => { |
49 | this.notificationsService.error(this.i18n('Error'), err.message) | 49 | this.notifier.error(err.message) |
50 | } | 50 | } |
51 | ) | 51 | ) |
52 | } | 52 | } |
diff --git a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts b/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts index ea7b0e118..895b19064 100644 --- a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts +++ b/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts | |||
@@ -3,7 +3,7 @@ import { VideoChannelService } from '@app/shared/video-channel/video-channel.ser | |||
3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { Subscription } from 'rxjs' | 5 | import { Subscription } from 'rxjs' |
6 | import { MarkdownService } from '@app/videos/shared' | 6 | import { MarkdownService } from '@app/shared/renderer' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-video-channel-about', | 9 | selector: 'my-video-channel-about', |
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts index 1f0744fb1..dea378a6e 100644 --- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts +++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts | |||
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Location } from '@angular/common' | 3 | import { Location } from '@angular/common' |
4 | import { immutableAssign } from '@app/shared/misc/utils' | 4 | import { immutableAssign } from '@app/shared/misc/utils' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { ConfirmService } from '../../core/confirm' | 6 | import { ConfirmService } from '../../core/confirm' |
8 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
@@ -13,6 +12,7 @@ import { tap } from 'rxjs/operators' | |||
13 | import { I18n } from '@ngx-translate/i18n-polyfill' | 12 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { Subscription } from 'rxjs' | 13 | import { Subscription } from 'rxjs' |
15 | import { ScreenService } from '@app/shared/misc/screen.service' | 14 | import { ScreenService } from '@app/shared/misc/screen.service' |
15 | import { Notifier } from '@app/core' | ||
16 | 16 | ||
17 | @Component({ | 17 | @Component({ |
18 | selector: 'my-video-channel-videos', | 18 | selector: 'my-video-channel-videos', |
@@ -35,7 +35,7 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
35 | protected router: Router, | 35 | protected router: Router, |
36 | protected route: ActivatedRoute, | 36 | protected route: ActivatedRoute, |
37 | protected authService: AuthService, | 37 | protected authService: AuthService, |
38 | protected notificationsService: NotificationsService, | 38 | protected notifier: Notifier, |
39 | protected confirmService: ConfirmService, | 39 | protected confirmService: ConfirmService, |
40 | protected location: Location, | 40 | protected location: Location, |
41 | protected screenService: ScreenService, | 41 | protected screenService: ScreenService, |
@@ -55,7 +55,7 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
55 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded | 55 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded |
56 | .subscribe(videoChannel => { | 56 | .subscribe(videoChannel => { |
57 | this.videoChannel = videoChannel | 57 | this.videoChannel = videoChannel |
58 | this.currentRoute = '/video-channels/' + this.videoChannel.uuid + '/videos' | 58 | this.currentRoute = '/video-channels/' + this.videoChannel.nameWithHost + '/videos' |
59 | 59 | ||
60 | this.reloadVideos() | 60 | this.reloadVideos() |
61 | this.generateSyndicationList() | 61 | this.generateSyndicationList() |
diff --git a/client/src/app/+video-channels/video-channels-routing.module.ts b/client/src/app/+video-channels/video-channels-routing.module.ts index 935578d2a..3ac3533d9 100644 --- a/client/src/app/+video-channels/video-channels-routing.module.ts +++ b/client/src/app/+video-channels/video-channels-routing.module.ts | |||
@@ -7,7 +7,7 @@ import { VideoChannelAboutComponent } from './video-channel-about/video-channel- | |||
7 | 7 | ||
8 | const videoChannelsRoutes: Routes = [ | 8 | const videoChannelsRoutes: Routes = [ |
9 | { | 9 | { |
10 | path: ':videoChannelId', | 10 | path: ':videoChannelName', |
11 | component: VideoChannelsComponent, | 11 | component: VideoChannelsComponent, |
12 | canActivateChild: [ MetaGuard ], | 12 | canActivateChild: [ MetaGuard ], |
13 | children: [ | 13 | children: [ |
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts index 0c5c814c7..41ff82e98 100644 --- a/client/src/app/+video-channels/video-channels.component.ts +++ b/client/src/app/+video-channels/video-channels.component.ts | |||
@@ -34,9 +34,9 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { | |||
34 | ngOnInit () { | 34 | ngOnInit () { |
35 | this.routeSub = this.route.params | 35 | this.routeSub = this.route.params |
36 | .pipe( | 36 | .pipe( |
37 | map(params => params[ 'videoChannelId' ]), | 37 | map(params => params[ 'videoChannelName' ]), |
38 | distinctUntilChanged(), | 38 | distinctUntilChanged(), |
39 | switchMap(videoChannelId => this.videoChannelService.getVideoChannel(videoChannelId)), | 39 | switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)), |
40 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) | 40 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) |
41 | ) | 41 | ) |
42 | .subscribe(videoChannel => this.videoChannel = videoChannel) | 42 | .subscribe(videoChannel => this.videoChannel = videoChannel) |
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 545d6aeda..cff37a7d6 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts | |||
@@ -43,7 +43,8 @@ const routes: Routes = [ | |||
43 | imports: [ | 43 | imports: [ |
44 | RouterModule.forRoot(routes, { | 44 | RouterModule.forRoot(routes, { |
45 | useHash: Boolean(history.pushState) === false, | 45 | useHash: Boolean(history.pushState) === false, |
46 | preloadingStrategy: PreloadSelectedModulesList | 46 | preloadingStrategy: PreloadSelectedModulesList, |
47 | anchorScrolling: 'enabled' | ||
47 | }) | 48 | }) |
48 | ], | 49 | ], |
49 | providers: [ | 50 | providers: [ |
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 03f7e88ed..d398d4f35 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -30,12 +30,27 @@ | |||
30 | 30 | ||
31 | <footer class="row"> | 31 | <footer class="row"> |
32 | <a href="https://joinpeertube.org" title="PeerTube website" target="_blank" rel="noopener noreferrer">PeerTube v{{ serverVersion }}{{ serverCommit }}</a> - | 32 | <a href="https://joinpeertube.org" title="PeerTube website" target="_blank" rel="noopener noreferrer">PeerTube v{{ serverVersion }}{{ serverCommit }}</a> - |
33 | <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" title="PeerTube license" target="_blank" rel="noopener noreferrer">CopyLeft 2015-2018</a> | 33 | <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" title="PeerTube license" target="_blank" rel="noopener noreferrer">CopyLeft 2015-2019</a> |
34 | </footer> | 34 | </footer> |
35 | </div> | 35 | </div> |
36 | </div> | 36 | </div> |
37 | </div> | 37 | </div> |
38 | 38 | ||
39 | <ngx-loading-bar [includeSpinner]="false"></ngx-loading-bar> | 39 | <ngx-loading-bar [includeSpinner]="false"></ngx-loading-bar> |
40 | |||
40 | <my-confirm></my-confirm> | 41 | <my-confirm></my-confirm> |
41 | <simple-notifications [options]="notificationOptions"></simple-notifications> | 42 | |
43 | <p-toast position="bottom-right"> | ||
44 | <ng-template let-message pTemplate="message"> | ||
45 | <div class="notification-block"> | ||
46 | <div class="message"> | ||
47 | <h3>{{ message.summary }}</h3> | ||
48 | <p>{{ message.detail }}</p> | ||
49 | </div> | ||
50 | |||
51 | <span *ngIf="message.severity === 'success'" class="glyphicon glyphicon-ok"></span> | ||
52 | <span *ngIf="message.severity === 'info'" class="glyphicon glyphicon-info-sign"></span> | ||
53 | <span *ngIf="message.severity === 'error'" class="glyphicon glyphicon-remove"></span> | ||
54 | </div> | ||
55 | </ng-template> | ||
56 | </p-toast> | ||
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index b51a81eb1..881f3ff31 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss | |||
@@ -91,8 +91,3 @@ footer { | |||
91 | height: $footer-height; | 91 | height: $footer-height; |
92 | justify-content: center; | 92 | justify-content: center; |
93 | } | 93 | } |
94 | |||
95 | simple-notifications { | ||
96 | position: relative; | ||
97 | z-index: 1500; | ||
98 | } | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index dc4d0bf6a..7583fdee8 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -15,19 +15,6 @@ import { fromEvent } from 'rxjs' | |||
15 | styleUrls: [ './app.component.scss' ] | 15 | styleUrls: [ './app.component.scss' ] |
16 | }) | 16 | }) |
17 | export class AppComponent implements OnInit { | 17 | export class AppComponent implements OnInit { |
18 | notificationOptions = { | ||
19 | timeOut: 5000, | ||
20 | lastOnBottom: true, | ||
21 | clickToClose: true, | ||
22 | maxLength: 0, | ||
23 | maxStack: 7, | ||
24 | showProgressBar: false, | ||
25 | pauseOnHover: false, | ||
26 | preventDuplicates: false, | ||
27 | preventLastDuplicates: 'visible', | ||
28 | rtl: false | ||
29 | } | ||
30 | |||
31 | isMenuDisplayed = true | 18 | isMenuDisplayed = true |
32 | isMenuChangedByUser = false | 19 | isMenuChangedByUser = false |
33 | 20 | ||
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 371199442..0bbc2e08b 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -12,13 +12,12 @@ import { AppComponent } from './app.component' | |||
12 | import { CoreModule } from './core' | 12 | import { CoreModule } from './core' |
13 | import { HeaderComponent } from './header' | 13 | import { HeaderComponent } from './header' |
14 | import { LoginModule } from './login' | 14 | import { LoginModule } from './login' |
15 | import { MenuComponent } from './menu' | 15 | import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu' |
16 | import { SharedModule } from './shared' | 16 | import { SharedModule } from './shared' |
17 | import { SignupModule } from './signup' | 17 | import { SignupModule } from './signup' |
18 | import { VideosModule } from './videos' | 18 | import { VideosModule } from './videos' |
19 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' | 19 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' |
20 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 20 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
21 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | ||
22 | import { SearchModule } from '@app/search' | 21 | import { SearchModule } from '@app/search' |
23 | 22 | ||
24 | export function metaFactory (serverService: ServerService): MetaLoader { | 23 | export function metaFactory (serverService: ServerService): MetaLoader { |
@@ -40,6 +39,7 @@ export function metaFactory (serverService: ServerService): MetaLoader { | |||
40 | 39 | ||
41 | MenuComponent, | 40 | MenuComponent, |
42 | LanguageChooserComponent, | 41 | LanguageChooserComponent, |
42 | AvatarNotificationComponent, | ||
43 | HeaderComponent | 43 | HeaderComponent |
44 | ], | 44 | ], |
45 | imports: [ | 45 | imports: [ |
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index acd13d9c5..abb11fdc2 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 1 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
2 | import { UserRight } from '../../../../../shared/models/users/user-right.enum' | 2 | import { UserRight } from '../../../../../shared/models/users/user-right.enum' |
3 | import { User as ServerUserModel } from '../../../../../shared/models/users/user.model' | ||
3 | // Do not use the barrel (dependency loop) | 4 | // Do not use the barrel (dependency loop) |
4 | import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' | 5 | import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' |
5 | import { User, UserConstructorHash } from '../../shared/users/user.model' | 6 | import { User } from '../../shared/users/user.model' |
6 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | 7 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' |
7 | 8 | ||
8 | export type TokenOptions = { | 9 | export type TokenOptions = { |
@@ -70,6 +71,7 @@ export class AuthUser extends User { | |||
70 | ID: 'id', | 71 | ID: 'id', |
71 | ROLE: 'role', | 72 | ROLE: 'role', |
72 | EMAIL: 'email', | 73 | EMAIL: 'email', |
74 | VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', | ||
73 | USERNAME: 'username', | 75 | USERNAME: 'username', |
74 | NSFW_POLICY: 'nsfw_policy', | 76 | NSFW_POLICY: 'nsfw_policy', |
75 | WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', | 77 | WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', |
@@ -89,7 +91,8 @@ export class AuthUser extends User { | |||
89 | role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole, | 91 | role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole, |
90 | nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType, | 92 | nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType, |
91 | webTorrentEnabled: peertubeLocalStorage.getItem(this.KEYS.WEBTORRENT_ENABLED) === 'true', | 93 | webTorrentEnabled: peertubeLocalStorage.getItem(this.KEYS.WEBTORRENT_ENABLED) === 'true', |
92 | autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true' | 94 | autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true', |
95 | videosHistoryEnabled: peertubeLocalStorage.getItem(this.KEYS.VIDEOS_HISTORY_ENABLED) === 'true' | ||
93 | }, | 96 | }, |
94 | Tokens.load() | 97 | Tokens.load() |
95 | ) | 98 | ) |
@@ -104,12 +107,13 @@ export class AuthUser extends User { | |||
104 | peertubeLocalStorage.removeItem(this.KEYS.ROLE) | 107 | peertubeLocalStorage.removeItem(this.KEYS.ROLE) |
105 | peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY) | 108 | peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY) |
106 | peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED) | 109 | peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED) |
110 | peertubeLocalStorage.removeItem(this.KEYS.VIDEOS_HISTORY_ENABLED) | ||
107 | peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO) | 111 | peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO) |
108 | peertubeLocalStorage.removeItem(this.KEYS.EMAIL) | 112 | peertubeLocalStorage.removeItem(this.KEYS.EMAIL) |
109 | Tokens.flush() | 113 | Tokens.flush() |
110 | } | 114 | } |
111 | 115 | ||
112 | constructor (userHash: UserConstructorHash, hashTokens: TokenOptions) { | 116 | constructor (userHash: Partial<ServerUserModel>, hashTokens: TokenOptions) { |
113 | super(userHash) | 117 | super(userHash) |
114 | this.tokens = new Tokens(hashTokens) | 118 | this.tokens = new Tokens(hashTokens) |
115 | } | 119 | } |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 443772c9e..eaa822e0f 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -3,18 +3,18 @@ import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' | |||
3 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' | 3 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { Router } from '@angular/router' | 5 | import { Router } from '@angular/router' |
6 | import { NotificationsService } from 'angular2-notifications' | 6 | import { Notifier } from '@app/core/notification/notifier.service' |
7 | import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared' | 7 | import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared' |
8 | import { User } from '../../../../../shared/models/users' | 8 | import { User } from '../../../../../shared/models/users' |
9 | import { UserLogin } from '../../../../../shared/models/users/user-login.model' | 9 | import { UserLogin } from '../../../../../shared/models/users/user-login.model' |
10 | import { environment } from '../../../environments/environment' | 10 | import { environment } from '../../../environments/environment' |
11 | import { RestExtractor } from '../../shared/rest' | 11 | import { RestExtractor } from '../../shared/rest/rest-extractor.service' |
12 | import { AuthStatus } from './auth-status.model' | 12 | import { AuthStatus } from './auth-status.model' |
13 | import { AuthUser } from './auth-user.model' | 13 | import { AuthUser } from './auth-user.model' |
14 | import { objectToUrlEncoded } from '@app/shared/misc/utils' | 14 | import { objectToUrlEncoded } from '@app/shared/misc/utils' |
15 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 15 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
16 | import { I18n } from '@ngx-translate/i18n-polyfill' | 16 | import { I18n } from '@ngx-translate/i18n-polyfill' |
17 | import { HotkeysService, Hotkey } from 'angular2-hotkeys' | 17 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
18 | 18 | ||
19 | interface UserLoginWithUsername extends UserLogin { | 19 | interface UserLoginWithUsername extends UserLogin { |
20 | access_token: string | 20 | access_token: string |
@@ -38,7 +38,6 @@ export class AuthService { | |||
38 | loginChangedSource: Observable<AuthStatus> | 38 | loginChangedSource: Observable<AuthStatus> |
39 | userInformationLoaded = new ReplaySubject<boolean>(1) | 39 | userInformationLoaded = new ReplaySubject<boolean>(1) |
40 | hotkeys: Hotkey[] | 40 | hotkeys: Hotkey[] |
41 | redirectUrl: string | ||
42 | 41 | ||
43 | private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID) | 42 | private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID) |
44 | private clientSecret: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET) | 43 | private clientSecret: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET) |
@@ -48,7 +47,7 @@ export class AuthService { | |||
48 | 47 | ||
49 | constructor ( | 48 | constructor ( |
50 | private http: HttpClient, | 49 | private http: HttpClient, |
51 | private notificationsService: NotificationsService, | 50 | private notifier: Notifier, |
52 | private hotkeysService: HotkeysService, | 51 | private hotkeysService: HotkeysService, |
53 | private restExtractor: RestExtractor, | 52 | private restExtractor: RestExtractor, |
54 | private router: Router, | 53 | private router: Router, |
@@ -106,9 +105,8 @@ export class AuthService { | |||
106 | ) | 105 | ) |
107 | } | 106 | } |
108 | 107 | ||
109 | // We put a bigger timeout | 108 | // We put a bigger timeout: this is an important message |
110 | // This is an important message | 109 | this.notifier.error(errorMessage, this.i18n('Error'), 7000) |
111 | this.notificationsService.error(this.i18n('Error'), errorMessage, { timeOut: 7000 }) | ||
112 | } | 110 | } |
113 | ) | 111 | ) |
114 | } | 112 | } |
@@ -178,8 +176,6 @@ export class AuthService { | |||
178 | this.setStatus(AuthStatus.LoggedOut) | 176 | this.setStatus(AuthStatus.LoggedOut) |
179 | 177 | ||
180 | this.hotkeysService.remove(this.hotkeys) | 178 | this.hotkeysService.remove(this.hotkeys) |
181 | |||
182 | this.redirectUrl = null | ||
183 | } | 179 | } |
184 | 180 | ||
185 | refreshAccessToken () { | 181 | refreshAccessToken () { |
diff --git a/client/src/app/core/auth/index.ts b/client/src/app/core/auth/index.ts index bc7bfec0e..8e5caa7ed 100644 --- a/client/src/app/core/auth/index.ts +++ b/client/src/app/core/auth/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './auth-status.model' | 1 | export * from './auth-status.model' |
2 | export * from './auth-user.model' | 2 | export * from './auth-user.model' |
3 | export * from './auth.service' | 3 | export * from './auth.service' |
4 | export * from '../routing/login-guard.service' | ||
diff --git a/client/src/app/core/confirm/index.ts b/client/src/app/core/confirm/index.ts index 44aabfc13..aca591e1a 100644 --- a/client/src/app/core/confirm/index.ts +++ b/client/src/app/core/confirm/index.ts | |||
@@ -1,2 +1 @@ | |||
1 | export * from './confirm.component' | ||
2 | export * from './confirm.service' | export * from './confirm.service' | |
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index df2ec696d..4ef3b1e73 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts | |||
@@ -7,16 +7,18 @@ import { LoadingBarModule } from '@ngx-loading-bar/core' | |||
7 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' | 7 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' |
8 | import { LoadingBarRouterModule } from '@ngx-loading-bar/router' | 8 | import { LoadingBarRouterModule } from '@ngx-loading-bar/router' |
9 | 9 | ||
10 | import { SimpleNotificationsModule } from 'angular2-notifications' | ||
11 | |||
12 | import { AuthService } from './auth' | 10 | import { AuthService } from './auth' |
13 | import { ConfirmComponent, ConfirmService } from './confirm' | 11 | import { ConfirmService } from './confirm' |
14 | import { throwIfAlreadyLoaded } from './module-import-guard' | 12 | import { throwIfAlreadyLoaded } from './module-import-guard' |
15 | import { LoginGuard, RedirectService, UserRightGuard } from './routing' | 13 | import { LoginGuard, RedirectService, UserRightGuard } from './routing' |
16 | import { ServerService } from './server' | 14 | import { ServerService } from './server' |
17 | import { ThemeService } from './theme' | 15 | import { ThemeService } from './theme' |
18 | import { HotkeyModule } from 'angular2-hotkeys' | 16 | import { HotkeyModule } from 'angular2-hotkeys' |
19 | import { CheatSheetComponent } from '@app/core/hotkeys' | 17 | import { CheatSheetComponent } from './hotkeys' |
18 | import { ToastModule } from 'primeng/toast' | ||
19 | import { Notifier } from './notification' | ||
20 | import { MessageService } from 'primeng/api' | ||
21 | import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service' | ||
20 | 22 | ||
21 | @NgModule({ | 23 | @NgModule({ |
22 | imports: [ | 24 | imports: [ |
@@ -25,11 +27,10 @@ import { CheatSheetComponent } from '@app/core/hotkeys' | |||
25 | FormsModule, | 27 | FormsModule, |
26 | BrowserAnimationsModule, | 28 | BrowserAnimationsModule, |
27 | 29 | ||
28 | SimpleNotificationsModule.forRoot(), | ||
29 | |||
30 | LoadingBarHttpClientModule, | 30 | LoadingBarHttpClientModule, |
31 | LoadingBarRouterModule, | 31 | LoadingBarRouterModule, |
32 | LoadingBarModule.forRoot(), | 32 | LoadingBarModule, |
33 | ToastModule, | ||
33 | 34 | ||
34 | HotkeyModule.forRoot({ | 35 | HotkeyModule.forRoot({ |
35 | cheatSheetCloseEsc: true | 36 | cheatSheetCloseEsc: true |
@@ -37,16 +38,15 @@ import { CheatSheetComponent } from '@app/core/hotkeys' | |||
37 | ], | 38 | ], |
38 | 39 | ||
39 | declarations: [ | 40 | declarations: [ |
40 | ConfirmComponent, | ||
41 | CheatSheetComponent | 41 | CheatSheetComponent |
42 | ], | 42 | ], |
43 | 43 | ||
44 | exports: [ | 44 | exports: [ |
45 | SimpleNotificationsModule, | ||
46 | LoadingBarHttpClientModule, | 45 | LoadingBarHttpClientModule, |
47 | LoadingBarModule, | 46 | LoadingBarModule, |
48 | 47 | ||
49 | ConfirmComponent, | 48 | ToastModule, |
49 | |||
50 | CheatSheetComponent | 50 | CheatSheetComponent |
51 | ], | 51 | ], |
52 | 52 | ||
@@ -57,7 +57,10 @@ import { CheatSheetComponent } from '@app/core/hotkeys' | |||
57 | ThemeService, | 57 | ThemeService, |
58 | LoginGuard, | 58 | LoginGuard, |
59 | UserRightGuard, | 59 | UserRightGuard, |
60 | RedirectService | 60 | RedirectService, |
61 | Notifier, | ||
62 | MessageService, | ||
63 | UserNotificationSocket | ||
61 | ] | 64 | ] |
62 | }) | 65 | }) |
63 | export class CoreModule { | 66 | export class CoreModule { |
diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts index 524589d74..f664aff41 100644 --- a/client/src/app/core/index.ts +++ b/client/src/app/core/index.ts | |||
@@ -2,6 +2,7 @@ export * from './auth' | |||
2 | export * from './confirm' | 2 | export * from './confirm' |
3 | export * from './routing' | 3 | export * from './routing' |
4 | export * from './server' | 4 | export * from './server' |
5 | export * from './notification' | ||
5 | export * from './theme' | 6 | export * from './theme' |
6 | 7 | ||
7 | export * from './core.module' | 8 | export * from './core.module' |
diff --git a/client/src/app/core/notification/index.ts b/client/src/app/core/notification/index.ts new file mode 100644 index 000000000..3e8d9ea65 --- /dev/null +++ b/client/src/app/core/notification/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './notifier.service' | ||
2 | export * from './user-notification-socket.service' | ||
diff --git a/client/src/app/core/notification/notifier.service.ts b/client/src/app/core/notification/notifier.service.ts new file mode 100644 index 000000000..9833c65a0 --- /dev/null +++ b/client/src/app/core/notification/notifier.service.ts | |||
@@ -0,0 +1,41 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { MessageService } from 'primeng/api' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | |||
5 | @Injectable() | ||
6 | export class Notifier { | ||
7 | readonly TIMEOUT = 5000 | ||
8 | |||
9 | constructor ( | ||
10 | private i18n: I18n, | ||
11 | private messageService: MessageService) { | ||
12 | } | ||
13 | |||
14 | info (text: string, title?: string, timeout?: number) { | ||
15 | if (!title) title = this.i18n('Info') | ||
16 | |||
17 | return this.notify('info', text, title, timeout) | ||
18 | } | ||
19 | |||
20 | error (text: string, title?: string, timeout?: number) { | ||
21 | if (!title) title = this.i18n('Error') | ||
22 | |||
23 | return this.notify('error', text, title, timeout) | ||
24 | } | ||
25 | |||
26 | success (text: string, title?: string, timeout?: number) { | ||
27 | if (!title) title = this.i18n('Success') | ||
28 | |||
29 | return this.notify('success', text, title, timeout) | ||
30 | } | ||
31 | |||
32 | private notify (severity: 'success' | 'info' | 'warn' | 'error', text: string, title: string, timeout?: number) { | ||
33 | this.messageService.add({ | ||
34 | severity, | ||
35 | summary: title, | ||
36 | detail: text, | ||
37 | closable: true, | ||
38 | life: timeout || this.TIMEOUT | ||
39 | }) | ||
40 | } | ||
41 | } | ||
diff --git a/client/src/app/core/notification/user-notification-socket.service.ts b/client/src/app/core/notification/user-notification-socket.service.ts new file mode 100644 index 000000000..f367d9ae4 --- /dev/null +++ b/client/src/app/core/notification/user-notification-socket.service.ts | |||
@@ -0,0 +1,41 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { environment } from '../../../environments/environment' | ||
3 | import { UserNotification as UserNotificationServer } from '../../../../../shared' | ||
4 | import { Subject } from 'rxjs' | ||
5 | import * as io from 'socket.io-client' | ||
6 | import { AuthService } from '../auth' | ||
7 | |||
8 | export type NotificationEvent = 'new' | 'read' | 'read-all' | ||
9 | |||
10 | @Injectable() | ||
11 | export class UserNotificationSocket { | ||
12 | private notificationSubject = new Subject<{ type: NotificationEvent, notification?: UserNotificationServer }>() | ||
13 | |||
14 | private socket: SocketIOClient.Socket | ||
15 | |||
16 | constructor ( | ||
17 | private auth: AuthService | ||
18 | ) {} | ||
19 | |||
20 | dispatch (type: NotificationEvent, notification?: UserNotificationServer) { | ||
21 | this.notificationSubject.next({ type, notification }) | ||
22 | } | ||
23 | |||
24 | getMyNotificationsSocket () { | ||
25 | const socket = this.getSocket() | ||
26 | |||
27 | socket.on('new-notification', (n: UserNotificationServer) => this.dispatch('new', n)) | ||
28 | |||
29 | return this.notificationSubject.asObservable() | ||
30 | } | ||
31 | |||
32 | private getSocket () { | ||
33 | if (this.socket) return this.socket | ||
34 | |||
35 | this.socket = io(environment.apiUrl + '/user-notifications', { | ||
36 | query: { accessToken: this.auth.getAccessToken() } | ||
37 | }) | ||
38 | |||
39 | return this.socket | ||
40 | } | ||
41 | } | ||
diff --git a/client/src/app/core/routing/login-guard.service.ts b/client/src/app/core/routing/login-guard.service.ts index 40ff8f505..7b1c37ee8 100644 --- a/client/src/app/core/routing/login-guard.service.ts +++ b/client/src/app/core/routing/login-guard.service.ts | |||
@@ -1,11 +1,5 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { | 2 | import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router' |
3 | ActivatedRouteSnapshot, | ||
4 | CanActivateChild, | ||
5 | RouterStateSnapshot, | ||
6 | CanActivate, | ||
7 | Router | ||
8 | } from '@angular/router' | ||
9 | 3 | ||
10 | import { AuthService } from '../auth/auth.service' | 4 | import { AuthService } from '../auth/auth.service' |
11 | 5 | ||
@@ -20,8 +14,6 @@ export class LoginGuard implements CanActivate, CanActivateChild { | |||
20 | canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { | 14 | canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { |
21 | if (this.auth.isLoggedIn() === true) return true | 15 | if (this.auth.isLoggedIn() === true) return true |
22 | 16 | ||
23 | this.auth.redirectUrl = state.url | ||
24 | |||
25 | this.router.navigate([ '/login' ]) | 17 | this.router.navigate([ '/login' ]) |
26 | return false | 18 | return false |
27 | } | 19 | } |
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts index 1881be117..e1db4097b 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { NavigationEnd, Router } from '@angular/router' |
3 | import { ServerService } from '../server' | 3 | import { ServerService } from '../server' |
4 | 4 | ||
5 | @Injectable() | 5 | @Injectable() |
@@ -8,6 +8,9 @@ export class RedirectService { | |||
8 | static INIT_DEFAULT_ROUTE = '/videos/trending' | 8 | static INIT_DEFAULT_ROUTE = '/videos/trending' |
9 | static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE | 9 | static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE |
10 | 10 | ||
11 | private previousUrl: string | ||
12 | private currentUrl: string | ||
13 | |||
11 | constructor ( | 14 | constructor ( |
12 | private router: Router, | 15 | private router: Router, |
13 | private serverService: ServerService | 16 | private serverService: ServerService |
@@ -18,6 +21,7 @@ export class RedirectService { | |||
18 | RedirectService.DEFAULT_ROUTE = config.instance.defaultClientRoute | 21 | RedirectService.DEFAULT_ROUTE = config.instance.defaultClientRoute |
19 | } | 22 | } |
20 | 23 | ||
24 | // Load default route | ||
21 | this.serverService.configLoaded | 25 | this.serverService.configLoaded |
22 | .subscribe(() => { | 26 | .subscribe(() => { |
23 | const defaultRouteConfig = this.serverService.getConfig().instance.defaultClientRoute | 27 | const defaultRouteConfig = this.serverService.getConfig().instance.defaultClientRoute |
@@ -26,6 +30,21 @@ export class RedirectService { | |||
26 | RedirectService.DEFAULT_ROUTE = defaultRouteConfig | 30 | RedirectService.DEFAULT_ROUTE = defaultRouteConfig |
27 | } | 31 | } |
28 | }) | 32 | }) |
33 | |||
34 | // Track previous url | ||
35 | this.currentUrl = this.router.url | ||
36 | router.events.subscribe(event => { | ||
37 | if (event instanceof NavigationEnd) { | ||
38 | this.previousUrl = this.currentUrl | ||
39 | this.currentUrl = event.url | ||
40 | } | ||
41 | }) | ||
42 | } | ||
43 | |||
44 | redirectToPreviousRoute () { | ||
45 | if (this.previousUrl) return this.router.navigateByUrl(this.previousUrl) | ||
46 | |||
47 | return this.redirectToHomepage() | ||
29 | } | 48 | } |
30 | 49 | ||
31 | redirectToHomepage (skipLocationChange = false) { | 50 | redirectToHomepage (skipLocationChange = false) { |
diff --git a/client/src/app/core/routing/user-right-guard.service.ts b/client/src/app/core/routing/user-right-guard.service.ts index 65d029977..50c3d8c19 100644 --- a/client/src/app/core/routing/user-right-guard.service.ts +++ b/client/src/app/core/routing/user-right-guard.service.ts | |||
@@ -7,7 +7,7 @@ import { | |||
7 | Router | 7 | Router |
8 | } from '@angular/router' | 8 | } from '@angular/router' |
9 | 9 | ||
10 | import { AuthService } from '../auth' | 10 | import { AuthService } from '../auth/auth.service' |
11 | 11 | ||
12 | @Injectable() | 12 | @Injectable() |
13 | export class UserRightGuard implements CanActivate, CanActivateChild { | 13 | export class UserRightGuard implements CanActivate, CanActivateChild { |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index da8bd26db..4ae72427b 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -13,6 +13,7 @@ import { sortBy } from '@app/shared/misc/utils' | |||
13 | 13 | ||
14 | @Injectable() | 14 | @Injectable() |
15 | export class ServerService { | 15 | export class ServerService { |
16 | private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server/' | ||
16 | private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/' | 17 | private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/' |
17 | private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' | 18 | private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' |
18 | private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' | 19 | private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' |
@@ -37,6 +38,12 @@ export class ServerService { | |||
37 | css: '' | 38 | css: '' |
38 | } | 39 | } |
39 | }, | 40 | }, |
41 | email: { | ||
42 | enabled: false | ||
43 | }, | ||
44 | contactForm: { | ||
45 | enabled: false | ||
46 | }, | ||
40 | serverVersion: 'Unknown', | 47 | serverVersion: 'Unknown', |
41 | signup: { | 48 | signup: { |
42 | allowed: false, | 49 | allowed: false, |
@@ -80,6 +87,11 @@ export class ServerService { | |||
80 | enabled: false | 87 | enabled: false |
81 | } | 88 | } |
82 | } | 89 | } |
90 | }, | ||
91 | trending: { | ||
92 | videos: { | ||
93 | intervalDays: 0 | ||
94 | } | ||
83 | } | 95 | } |
84 | } | 96 | } |
85 | private videoCategories: Array<VideoConstant<number>> = [] | 97 | private videoCategories: Array<VideoConstant<number>> = [] |
@@ -141,10 +153,6 @@ export class ServerService { | |||
141 | return this.videoPrivacies | 153 | return this.videoPrivacies |
142 | } | 154 | } |
143 | 155 | ||
144 | getAbout () { | ||
145 | return this.http.get<About>(ServerService.BASE_CONFIG_URL + '/about') | ||
146 | } | ||
147 | |||
148 | private loadVideoAttributeEnum ( | 156 | private loadVideoAttributeEnum ( |
149 | attributeName: 'categories' | 'licences' | 'languages' | 'privacies', | 157 | attributeName: 'categories' | 'licences' | 'languages' | 'privacies', |
150 | hashToPopulate: VideoConstant<string | number>[], | 158 | hashToPopulate: VideoConstant<string | number>[], |
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html index c23e0c55d..46a87c79c 100644 --- a/client/src/app/header/header.component.html +++ b/client/src/app/header/header.component.html | |||
@@ -5,6 +5,6 @@ | |||
5 | <span (click)="doSearch()" class="icon icon-search"></span> | 5 | <span (click)="doSearch()" class="icon icon-search"></span> |
6 | 6 | ||
7 | <a class="upload-button" routerLink="/videos/upload"> | 7 | <a class="upload-button" routerLink="/videos/upload"> |
8 | <span class="icon icon-upload"></span> | 8 | <my-global-icon iconName="upload"></my-global-icon> |
9 | <span i18n class="upload-button-label">Upload</span> | 9 | <span i18n class="upload-button-label">Upload</span> |
10 | </a> | 10 | </a> |
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss index 2f9820665..cea415d9b 100644 --- a/client/src/app/header/header.component.scss +++ b/client/src/app/header/header.component.scss | |||
@@ -6,6 +6,7 @@ | |||
6 | padding-left: 10px; | 6 | padding-left: 10px; |
7 | margin-right: 15px; | 7 | margin-right: 15px; |
8 | padding-right: 40px; // For the search icon | 8 | padding-right: 40px; // For the search icon |
9 | font-size: 14px; | ||
9 | 10 | ||
10 | &::placeholder { | 11 | &::placeholder { |
11 | color: var(--inputPlaceholderColor); | 12 | color: var(--inputPlaceholderColor); |
@@ -40,6 +41,7 @@ | |||
40 | .upload-button { | 41 | .upload-button { |
41 | @include peertube-button-link; | 42 | @include peertube-button-link; |
42 | @include orange-button; | 43 | @include orange-button; |
44 | @include button-with-icon(22px, 3px, -1px); | ||
43 | 45 | ||
44 | margin-right: 25px; | 46 | margin-right: 25px; |
45 | 47 | ||
@@ -47,15 +49,6 @@ | |||
47 | margin-right: 0; | 49 | margin-right: 0; |
48 | } | 50 | } |
49 | 51 | ||
50 | .icon.icon-upload { | ||
51 | @include icon(22px); | ||
52 | |||
53 | background-image: url('../../assets/images/header/upload-white.svg'); | ||
54 | height: 24px; | ||
55 | vertical-align: middle; | ||
56 | margin-right: 6px; | ||
57 | } | ||
58 | |||
59 | @media screen and (max-width: 600px) { | 52 | @media screen and (max-width: 600px) { |
60 | margin-right: 10px; | 53 | margin-right: 10px; |
61 | padding: 0 10px; | 54 | padding: 0 10px; |
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 93dbed525..4efe3fb22 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -55,11 +55,17 @@ | |||
55 | <ng-template #forgotPasswordModal> | 55 | <ng-template #forgotPasswordModal> |
56 | <div class="modal-header"> | 56 | <div class="modal-header"> |
57 | <h4 i18n class="modal-title">Forgot your password</h4> | 57 | <h4 i18n class="modal-title">Forgot your password</h4> |
58 | <span class="close" aria-hidden="true" (click)="hideForgotPasswordModal()"></span> | 58 | |
59 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideForgotPasswordModal()"></my-global-icon> | ||
59 | </div> | 60 | </div> |
60 | 61 | ||
61 | <div class="modal-body"> | 62 | <div class="modal-body"> |
62 | <div class="form-group"> | 63 | |
64 | <div *ngIf="isEmailDisabled()" class="alert alert-danger" i18n> | ||
65 | We are sorry, you cannot recover you password because your instance administrator did not configure the PeerTube email system. | ||
66 | </div> | ||
67 | |||
68 | <div class="form-group" [hidden]="isEmailDisabled()"> | ||
63 | <label i18n for="forgot-password-email">Email</label> | 69 | <label i18n for="forgot-password-email">Email</label> |
64 | <input | 70 | <input |
65 | type="email" id="forgot-password-email" i18n-placeholder placeholder="Email address" required | 71 | type="email" id="forgot-password-email" i18n-placeholder placeholder="Email address" required |
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index 7553e6456..fc2442c0e 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' |
2 | import { RedirectService, ServerService } from '@app/core' | 2 | import { Notifier, RedirectService, ServerService } from '@app/core' |
3 | import { UserService } from '@app/shared' | 3 | import { UserService } from '@app/shared' |
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { AuthService } from '../core' | 4 | import { AuthService } from '../core' |
6 | import { FormReactive } from '../shared' | 5 | import { FormReactive } from '../shared' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -19,7 +18,6 @@ import { Router } from '@angular/router' | |||
19 | export class LoginComponent extends FormReactive implements OnInit { | 18 | export class LoginComponent extends FormReactive implements OnInit { |
20 | @ViewChild('emailInput') input: ElementRef | 19 | @ViewChild('emailInput') input: ElementRef |
21 | @ViewChild('forgotPasswordModal') forgotPasswordModal: ElementRef | 20 | @ViewChild('forgotPasswordModal') forgotPasswordModal: ElementRef |
22 | @ViewChild('forgotPasswordEmailInput') forgotPasswordEmailInput: ElementRef | ||
23 | 21 | ||
24 | error: string = null | 22 | error: string = null |
25 | forgotPasswordEmail = '' | 23 | forgotPasswordEmail = '' |
@@ -35,7 +33,7 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
35 | private userService: UserService, | 33 | private userService: UserService, |
36 | private serverService: ServerService, | 34 | private serverService: ServerService, |
37 | private redirectService: RedirectService, | 35 | private redirectService: RedirectService, |
38 | private notificationsService: NotificationsService, | 36 | private notifier: Notifier, |
39 | private i18n: I18n | 37 | private i18n: I18n |
40 | ) { | 38 | ) { |
41 | super() | 39 | super() |
@@ -45,6 +43,10 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
45 | return this.serverService.getConfig().signup.allowed === true | 43 | return this.serverService.getConfig().signup.allowed === true |
46 | } | 44 | } |
47 | 45 | ||
46 | isEmailDisabled () { | ||
47 | return this.serverService.getConfig().email.enabled === false | ||
48 | } | ||
49 | |||
48 | ngOnInit () { | 50 | ngOnInit () { |
49 | this.buildForm({ | 51 | this.buildForm({ |
50 | username: this.loginValidatorsService.LOGIN_USERNAME, | 52 | username: this.loginValidatorsService.LOGIN_USERNAME, |
@@ -61,7 +63,7 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
61 | 63 | ||
62 | this.authService.login(username, password) | 64 | this.authService.login(username, password) |
63 | .subscribe( | 65 | .subscribe( |
64 | () => this.redirect(), | 66 | () => this.redirectService.redirectToPreviousRoute(), |
65 | 67 | ||
66 | err => { | 68 | err => { |
67 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.') | 69 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.') |
@@ -71,15 +73,6 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
71 | ) | 73 | ) |
72 | } | 74 | } |
73 | 75 | ||
74 | redirect () { | ||
75 | const redirect = this.authService.redirectUrl | ||
76 | if (redirect) { | ||
77 | this.router.navigate([ redirect ]) | ||
78 | } else { | ||
79 | this.redirectService.redirectToHomepage() | ||
80 | } | ||
81 | } | ||
82 | |||
83 | askResetPassword () { | 76 | askResetPassword () { |
84 | this.userService.askResetPassword(this.forgotPasswordEmail) | 77 | this.userService.askResetPassword(this.forgotPasswordEmail) |
85 | .subscribe( | 78 | .subscribe( |
@@ -88,18 +81,14 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
88 | 'An email with the reset password instructions will be sent to {{email}}.', | 81 | 'An email with the reset password instructions will be sent to {{email}}.', |
89 | { email: this.forgotPasswordEmail } | 82 | { email: this.forgotPasswordEmail } |
90 | ) | 83 | ) |
91 | this.notificationsService.success(this.i18n('Success'), message) | 84 | this.notifier.success(message) |
92 | this.hideForgotPasswordModal() | 85 | this.hideForgotPasswordModal() |
93 | }, | 86 | }, |
94 | 87 | ||
95 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 88 | err => this.notifier.error(err.message) |
96 | ) | 89 | ) |
97 | } | 90 | } |
98 | 91 | ||
99 | onForgotPasswordModalShown () { | ||
100 | this.forgotPasswordEmailInput.nativeElement.focus() | ||
101 | } | ||
102 | |||
103 | openForgotPasswordModal () { | 92 | openForgotPasswordModal () { |
104 | this.openedForgotPasswordModal = this.modalService.open(this.forgotPasswordModal) | 93 | this.openedForgotPasswordModal = this.modalService.open(this.forgotPasswordModal) |
105 | } | 94 | } |
diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/avatar-notification.component.html new file mode 100644 index 000000000..4ef3f0e89 --- /dev/null +++ b/client/src/app/menu/avatar-notification.component.html | |||
@@ -0,0 +1,23 @@ | |||
1 | <div | ||
2 | [ngbPopover]="popContent" autoClose="outside" placement="bottom-left" container="body" popoverClass="popover-notifications" | ||
3 | i18n-title title="View your notifications" class="notification-avatar" #popover="ngbPopover" | ||
4 | > | ||
5 | <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div> | ||
6 | |||
7 | <img [src]="user.accountAvatarUrl" alt="Avatar" /> | ||
8 | </div> | ||
9 | |||
10 | <ng-template #popContent> | ||
11 | <div class="notifications-header"> | ||
12 | <div i18n>Notifications</div> | ||
13 | |||
14 | <a | ||
15 | i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog" | ||
16 | routerLink="/my-account/settings" fragment="notifications" | ||
17 | ></a> | ||
18 | </div> | ||
19 | |||
20 | <my-user-notifications [ignoreLoadingBar]="true" [infiniteScroll]="false" itemsPerPage="10"></my-user-notifications> | ||
21 | |||
22 | <a class="all-notifications" routerLink="/my-account/notifications" i18n>See all your notifications</a> | ||
23 | </ng-template> | ||
diff --git a/client/src/app/menu/avatar-notification.component.scss b/client/src/app/menu/avatar-notification.component.scss new file mode 100644 index 000000000..e785db788 --- /dev/null +++ b/client/src/app/menu/avatar-notification.component.scss | |||
@@ -0,0 +1,91 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | /deep/ { | ||
5 | .popover-notifications.popover { | ||
6 | max-width: none; | ||
7 | |||
8 | .popover-body { | ||
9 | padding: 0; | ||
10 | font-size: 14px; | ||
11 | font-family: $main-fonts; | ||
12 | overflow-y: auto; | ||
13 | max-height: 500px; | ||
14 | width: 400px; | ||
15 | box-shadow: 0 6px 14px rgba(0, 0, 0, 0.30); | ||
16 | |||
17 | .notifications-header { | ||
18 | display: flex; | ||
19 | justify-content: space-between; | ||
20 | |||
21 | background-color: rgba(0, 0, 0, 0.10); | ||
22 | align-items: center; | ||
23 | padding: 0 10px; | ||
24 | font-size: 16px; | ||
25 | height: 50px; | ||
26 | |||
27 | a { | ||
28 | @include disable-default-a-behaviour; | ||
29 | |||
30 | color: rgba(20, 20, 20, 0.5); | ||
31 | |||
32 | &:hover { | ||
33 | color: rgba(20, 20, 20, 0.8); | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | .all-notifications { | ||
39 | display: flex; | ||
40 | align-items: center; | ||
41 | justify-content: center; | ||
42 | font-weight: $font-semibold; | ||
43 | color: var(--mainForegroundColor); | ||
44 | padding: 7px 0; | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | } | ||
49 | |||
50 | .notification-avatar { | ||
51 | cursor: pointer; | ||
52 | position: relative; | ||
53 | |||
54 | img, | ||
55 | .unread-notifications { | ||
56 | margin-left: 20px; | ||
57 | } | ||
58 | |||
59 | img { | ||
60 | @include avatar(34px); | ||
61 | |||
62 | margin-right: 10px; | ||
63 | } | ||
64 | |||
65 | .unread-notifications { | ||
66 | position: absolute; | ||
67 | top: -5px; | ||
68 | left: -5px; | ||
69 | |||
70 | display: flex; | ||
71 | align-items: center; | ||
72 | justify-content: center; | ||
73 | |||
74 | background-color: var(--mainColor); | ||
75 | color: var(#fff); | ||
76 | font-size: 10px; | ||
77 | font-weight: $font-semibold; | ||
78 | |||
79 | border-radius: 15px; | ||
80 | width: 15px; | ||
81 | height: 15px; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | @media screen and (max-width: $mobile-view) { | ||
86 | /deep/ { | ||
87 | .popover-notifications.popover .popover-body { | ||
88 | width: 400px; | ||
89 | } | ||
90 | } | ||
91 | } | ||
diff --git a/client/src/app/menu/avatar-notification.component.ts b/client/src/app/menu/avatar-notification.component.ts new file mode 100644 index 000000000..f1af08096 --- /dev/null +++ b/client/src/app/menu/avatar-notification.component.ts | |||
@@ -0,0 +1,65 @@ | |||
1 | import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' | ||
2 | import { User } from '../shared/users/user.model' | ||
3 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | ||
4 | import { Subscription } from 'rxjs' | ||
5 | import { Notifier, UserNotificationSocket } from '@app/core' | ||
6 | import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||
7 | import { NavigationEnd, Router } from '@angular/router' | ||
8 | import { filter } from 'rxjs/operators' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-avatar-notification', | ||
12 | templateUrl: './avatar-notification.component.html', | ||
13 | styleUrls: [ './avatar-notification.component.scss' ] | ||
14 | }) | ||
15 | export class AvatarNotificationComponent implements OnInit, OnDestroy { | ||
16 | @ViewChild('popover') popover: NgbPopover | ||
17 | @Input() user: User | ||
18 | |||
19 | unreadNotifications = 0 | ||
20 | |||
21 | private notificationSub: Subscription | ||
22 | private routeSub: Subscription | ||
23 | |||
24 | constructor ( | ||
25 | private userNotificationService: UserNotificationService, | ||
26 | private userNotificationSocket: UserNotificationSocket, | ||
27 | private notifier: Notifier, | ||
28 | private router: Router | ||
29 | ) {} | ||
30 | |||
31 | ngOnInit () { | ||
32 | this.userNotificationService.countUnreadNotifications() | ||
33 | .subscribe( | ||
34 | result => { | ||
35 | this.unreadNotifications = Math.min(result, 99) // Limit number to 99 | ||
36 | this.subscribeToNotifications() | ||
37 | }, | ||
38 | |||
39 | err => this.notifier.error(err.message) | ||
40 | ) | ||
41 | |||
42 | this.routeSub = this.router.events | ||
43 | .pipe(filter(event => event instanceof NavigationEnd)) | ||
44 | .subscribe(() => this.closePopover()) | ||
45 | } | ||
46 | |||
47 | ngOnDestroy () { | ||
48 | if (this.notificationSub) this.notificationSub.unsubscribe() | ||
49 | if (this.routeSub) this.routeSub.unsubscribe() | ||
50 | } | ||
51 | |||
52 | closePopover () { | ||
53 | this.popover.close() | ||
54 | } | ||
55 | |||
56 | private subscribeToNotifications () { | ||
57 | this.notificationSub = this.userNotificationSocket.getMyNotificationsSocket() | ||
58 | .subscribe(data => { | ||
59 | if (data.type === 'new') return this.unreadNotifications++ | ||
60 | if (data.type === 'read') return this.unreadNotifications-- | ||
61 | if (data.type === 'read-all') return this.unreadNotifications = 0 | ||
62 | }) | ||
63 | } | ||
64 | |||
65 | } | ||
diff --git a/client/src/app/menu/index.ts b/client/src/app/menu/index.ts index 421271c12..39dbde750 100644 --- a/client/src/app/menu/index.ts +++ b/client/src/app/menu/index.ts | |||
@@ -1 +1,3 @@ | |||
1 | export * from './language-chooser.component' | ||
2 | export * from './avatar-notification.component' | ||
1 | export * from './menu.component' | 3 | export * from './menu.component' |
diff --git a/client/src/app/menu/language-chooser.component.html b/client/src/app/menu/language-chooser.component.html index c37bf2826..a62b33dda 100644 --- a/client/src/app/menu/language-chooser.component.html +++ b/client/src/app/menu/language-chooser.component.html | |||
@@ -1,9 +1,14 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Change the language</h4> | 3 | <h4 i18n class="modal-title">Change the language</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | |||
8 | <a i18n class="help-to-translate" target="_blank" rel="noreferrer noopener" href="https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/translation.md"> | ||
9 | Help to translate PeerTube! | ||
10 | </a> | ||
11 | |||
7 | <div class="modal-body"> | 12 | <div class="modal-body"> |
8 | <a *ngFor="let lang of languages" [href]="buildLanguageLink(lang)">{{ lang.label }}</a> | 13 | <a *ngFor="let lang of languages" [href]="buildLanguageLink(lang)">{{ lang.label }}</a> |
9 | </div> | 14 | </div> |
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss index 944e86f46..72deb3952 100644 --- a/client/src/app/menu/language-chooser.component.scss +++ b/client/src/app/menu/language-chooser.component.scss | |||
@@ -1,6 +1,11 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .help-to-translate { | ||
5 | @include peertube-button-link; | ||
6 | @include orange-button; | ||
7 | } | ||
8 | |||
4 | .modal-body { | 9 | .modal-body { |
5 | text-align: center; | 10 | text-align: center; |
6 | 11 | ||
@@ -9,4 +14,4 @@ | |||
9 | font-size: 16px; | 14 | font-size: 16px; |
10 | margin: 15px; | 15 | margin: 15px; |
11 | } | 16 | } |
12 | } \ No newline at end of file | 17 | } |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index e04bdf3d6..aa5bfa9c9 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -2,9 +2,7 @@ | |||
2 | <menu> | 2 | <menu> |
3 | <div class="top-menu"> | 3 | <div class="top-menu"> |
4 | <div *ngIf="isLoggedIn" class="logged-in-block"> | 4 | <div *ngIf="isLoggedIn" class="logged-in-block"> |
5 | <a routerLink="/my-account/settings"> | 5 | <my-avatar-notification [user]="user"></my-avatar-notification> |
6 | <img [src]="user.accountAvatarUrl" alt="Avatar" /> | ||
7 | </a> | ||
8 | 6 | ||
9 | <div class="logged-in-info"> | 7 | <div class="logged-in-info"> |
10 | <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a> | 8 | <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a> |
@@ -97,4 +95,4 @@ | |||
97 | </menu> | 95 | </menu> |
98 | </div> | 96 | </div> |
99 | 97 | ||
100 | <my-language-chooser #languageChooserModal></my-language-chooser> \ No newline at end of file | 98 | <my-language-chooser #languageChooserModal></my-language-chooser> |
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index b271ebfd2..f30b89413 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -16,7 +16,7 @@ menu { | |||
16 | height: 100%; | 16 | height: 100%; |
17 | white-space: nowrap; | 17 | white-space: nowrap; |
18 | text-overflow: ellipsis; | 18 | text-overflow: ellipsis; |
19 | overflow: hidden; | 19 | overflow: auto; |
20 | color: var(--menuForegroundColor); | 20 | color: var(--menuForegroundColor); |
21 | display: flex; | 21 | display: flex; |
22 | flex-direction: column; | 22 | flex-direction: column; |
@@ -39,13 +39,6 @@ menu { | |||
39 | justify-content: center; | 39 | justify-content: center; |
40 | margin-bottom: 35px; | 40 | margin-bottom: 35px; |
41 | 41 | ||
42 | img { | ||
43 | @include avatar(34px); | ||
44 | |||
45 | margin-left: 20px; | ||
46 | margin-right: 10px; | ||
47 | } | ||
48 | |||
49 | .logged-in-info { | 42 | .logged-in-info { |
50 | flex-grow: 1; | 43 | flex-grow: 1; |
51 | 44 | ||
@@ -250,7 +243,7 @@ menu { | |||
250 | } | 243 | } |
251 | } | 244 | } |
252 | 245 | ||
253 | @media screen and (max-width: 400px) { | 246 | @media screen and (max-width: $mobile-view) { |
254 | .menu-wrapper { | 247 | .menu-wrapper { |
255 | width: 100% !important; | 248 | width: 100% !important; |
256 | } | 249 | } |
diff --git a/client/src/app/reset-password/reset-password.component.ts b/client/src/app/reset-password/reset-password.component.ts index af1298de6..07b93ee73 100644 --- a/client/src/app/reset-password/reset-password.component.ts +++ b/client/src/app/reset-password/reset-password.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { UserService, UserValidatorsService } from '@app/shared' | 3 | import { UserService, UserValidatorsService, FormReactive } from '@app/shared' |
4 | import { NotificationsService } from 'angular2-notifications' | 4 | import { Notifier } from '@app/core' |
5 | import { FormReactive } from '../shared' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { ResetPasswordValidatorsService } from '@app/shared/forms/form-validators/reset-password-validators.service' | 7 | import { ResetPasswordValidatorsService } from '@app/shared/forms/form-validators/reset-password-validators.service' |
@@ -22,7 +21,7 @@ export class ResetPasswordComponent extends FormReactive implements OnInit { | |||
22 | private resetPasswordValidatorsService: ResetPasswordValidatorsService, | 21 | private resetPasswordValidatorsService: ResetPasswordValidatorsService, |
23 | private userValidatorsService: UserValidatorsService, | 22 | private userValidatorsService: UserValidatorsService, |
24 | private userService: UserService, | 23 | private userService: UserService, |
25 | private notificationsService: NotificationsService, | 24 | private notifier: Notifier, |
26 | private router: Router, | 25 | private router: Router, |
27 | private route: ActivatedRoute, | 26 | private route: ActivatedRoute, |
28 | private i18n: I18n | 27 | private i18n: I18n |
@@ -40,7 +39,7 @@ export class ResetPasswordComponent extends FormReactive implements OnInit { | |||
40 | this.verificationString = this.route.snapshot.queryParams['verificationString'] | 39 | this.verificationString = this.route.snapshot.queryParams['verificationString'] |
41 | 40 | ||
42 | if (!this.userId || !this.verificationString) { | 41 | if (!this.userId || !this.verificationString) { |
43 | this.notificationsService.error(this.i18n('Error'), this.i18n('Unable to find user id or verification string.')) | 42 | this.notifier.error(this.i18n('Unable to find user id or verification string.')) |
44 | this.router.navigate([ '/' ]) | 43 | this.router.navigate([ '/' ]) |
45 | } | 44 | } |
46 | } | 45 | } |
@@ -49,11 +48,11 @@ export class ResetPasswordComponent extends FormReactive implements OnInit { | |||
49 | this.userService.resetPassword(this.userId, this.verificationString, this.form.value.password) | 48 | this.userService.resetPassword(this.userId, this.verificationString, this.form.value.password) |
50 | .subscribe( | 49 | .subscribe( |
51 | () => { | 50 | () => { |
52 | this.notificationsService.success(this.i18n('Success'), this.i18n('Your password has been successfully reset!')) | 51 | this.notifier.success(this.i18n('Your password has been successfully reset!')) |
53 | this.router.navigate([ '/login' ]) | 52 | this.router.navigate([ '/login' ]) |
54 | }, | 53 | }, |
55 | 54 | ||
56 | err => this.notificationsService.error('Error', err.message) | 55 | err => this.notifier.error(err.message) |
57 | ) | 56 | ) |
58 | } | 57 | } |
59 | 58 | ||
diff --git a/client/src/app/search/search-filters.component.ts b/client/src/app/search/search-filters.component.ts index 8d7f84ac1..3fdc6df35 100644 --- a/client/src/app/search/search-filters.component.ts +++ b/client/src/app/search/search-filters.component.ts | |||
@@ -1,10 +1,6 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2 | import { ActivatedRoute } from '@angular/router' | 2 | import { ServerService } from '@app/core' |
3 | import { RedirectService, ServerService } from '@app/core' | ||
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { SearchService } from '@app/search/search.service' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { MetaService } from '@ngx-meta/core' | ||
8 | import { AdvancedSearch } from '@app/search/advanced-search.model' | 4 | import { AdvancedSearch } from '@app/search/advanced-search.model' |
9 | import { VideoConstant } from '../../../../shared' | 5 | import { VideoConstant } from '../../../../shared' |
10 | 6 | ||
diff --git a/client/src/app/search/search.component.html b/client/src/app/search/search.component.html index 3a87ea1de..82a5f0f26 100644 --- a/client/src/app/search/search.component.html +++ b/client/src/app/search/search.component.html | |||
@@ -48,7 +48,7 @@ | |||
48 | </div> | 48 | </div> |
49 | 49 | ||
50 | <div *ngIf="isVideo(result)" class="entry video"> | 50 | <div *ngIf="isVideo(result)" class="entry video"> |
51 | <my-video-thumbnail [video]="result"></my-video-thumbnail> | 51 | <my-video-thumbnail [video]="result" [nsfw]="isVideoBlur(result)"></my-video-thumbnail> |
52 | 52 | ||
53 | <div class="video-info"> | 53 | <div class="video-info"> |
54 | <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', result.uuid]" [attr.title]="result.name">{{ result.name }}</a> | 54 | <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', result.uuid]" [attr.title]="result.name">{{ result.name }}</a> |
diff --git a/client/src/app/search/search.component.scss b/client/src/app/search/search.component.scss index 3e074621b..6de13d276 100644 --- a/client/src/app/search/search.component.scss +++ b/client/src/app/search/search.component.scss | |||
@@ -87,10 +87,10 @@ | |||
87 | text-overflow: ellipsis; | 87 | text-overflow: ellipsis; |
88 | white-space: nowrap; | 88 | white-space: nowrap; |
89 | font-size: 14px; | 89 | font-size: 14px; |
90 | color: #585858; | 90 | color: $grey-foreground-color; |
91 | 91 | ||
92 | &:hover { | 92 | &:hover { |
93 | color: #303030; | 93 | color: $grey-foreground-hover-color; |
94 | } | 94 | } |
95 | } | 95 | } |
96 | } | 96 | } |
diff --git a/client/src/app/search/search.component.ts b/client/src/app/search/search.component.ts index 3d17e6d96..c4a4b1fde 100644 --- a/client/src/app/search/search.component.ts +++ b/client/src/app/search/search.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { AuthService } from '@app/core' | 3 | import { AuthService, Notifier, ServerService } from '@app/core' |
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { forkJoin, Subscription } from 'rxjs' | 4 | import { forkJoin, Subscription } from 'rxjs' |
6 | import { SearchService } from '@app/search/search.service' | 5 | import { SearchService } from '@app/search/search.service' |
7 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | 6 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' |
@@ -40,9 +39,10 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
40 | private route: ActivatedRoute, | 39 | private route: ActivatedRoute, |
41 | private router: Router, | 40 | private router: Router, |
42 | private metaService: MetaService, | 41 | private metaService: MetaService, |
43 | private notificationsService: NotificationsService, | 42 | private notifier: Notifier, |
44 | private searchService: SearchService, | 43 | private searchService: SearchService, |
45 | private authService: AuthService | 44 | private authService: AuthService, |
45 | private serverService: ServerService | ||
46 | ) { } | 46 | ) { } |
47 | 47 | ||
48 | ngOnInit () { | 48 | ngOnInit () { |
@@ -68,7 +68,7 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
68 | this.search() | 68 | this.search() |
69 | }, | 69 | }, |
70 | 70 | ||
71 | err => this.notificationsService.error('Error', err.text) | 71 | err => this.notifier.error(err.text) |
72 | ) | 72 | ) |
73 | } | 73 | } |
74 | 74 | ||
@@ -76,6 +76,10 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
76 | if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe() | 76 | if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe() |
77 | } | 77 | } |
78 | 78 | ||
79 | isVideoBlur (video: Video) { | ||
80 | return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig()) | ||
81 | } | ||
82 | |||
79 | isVideoChannel (d: VideoChannel | Video): d is VideoChannel { | 83 | isVideoChannel (d: VideoChannel | Video): d is VideoChannel { |
80 | return d instanceof VideoChannel | 84 | return d instanceof VideoChannel |
81 | } | 85 | } |
@@ -112,9 +116,7 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
112 | this.firstSearch = false | 116 | this.firstSearch = false |
113 | }, | 117 | }, |
114 | 118 | ||
115 | error => { | 119 | err => this.notifier.error(err.message) |
116 | this.notificationsService.error(this.i18n('Error'), error.message) | ||
117 | } | ||
118 | ) | 120 | ) |
119 | 121 | ||
120 | } | 122 | } |
diff --git a/client/src/app/shared/actor/actor.model.ts b/client/src/app/shared/actor/actor.model.ts index 811afb449..adecec1fc 100644 --- a/client/src/app/shared/actor/actor.model.ts +++ b/client/src/app/shared/actor/actor.model.ts | |||
@@ -16,7 +16,7 @@ export abstract class Actor implements ActorServer { | |||
16 | 16 | ||
17 | avatarUrl: string | 17 | avatarUrl: string |
18 | 18 | ||
19 | static GET_ACTOR_AVATAR_URL (actor: { avatar: Avatar }) { | 19 | static GET_ACTOR_AVATAR_URL (actor: { avatar?: { path: string } }) { |
20 | const absoluteAPIUrl = getAbsoluteAPIUrl() | 20 | const absoluteAPIUrl = getAbsoluteAPIUrl() |
21 | 21 | ||
22 | if (actor && actor.avatar) return absoluteAPIUrl + actor.avatar.path | 22 | if (actor && actor.avatar) return absoluteAPIUrl + actor.avatar.path |
diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html index 90651f217..114b1d71f 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.html +++ b/client/src/app/shared/buttons/action-dropdown.component.html | |||
@@ -3,7 +3,7 @@ | |||
3 | class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }" | 3 | class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }" |
4 | ngbDropdownToggle role="button" | 4 | ngbDropdownToggle role="button" |
5 | > | 5 | > |
6 | <span *ngIf="!label" class="icon icon-action"></span> | 6 | <my-global-icon *ngIf="!label" class="more-icon" iconName="more"></my-global-icon> |
7 | <span *ngIf="label" class="dropdown-toggle">{{ label }}</span> | 7 | <span *ngIf="label" class="dropdown-toggle">{{ label }}</span> |
8 | </div> | 8 | </div> |
9 | 9 | ||
diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/buttons/action-dropdown.component.scss index a4fcceeee..985b2ca88 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.scss +++ b/client/src/app/shared/buttons/action-dropdown.component.scss | |||
@@ -24,14 +24,11 @@ | |||
24 | } | 24 | } |
25 | 25 | ||
26 | &:hover, &:active, &:focus { | 26 | &:hover, &:active, &:focus { |
27 | background-color: $grey-color; | 27 | background-color: $grey-background-color; |
28 | } | 28 | } |
29 | 29 | ||
30 | .icon-action { | 30 | .more-icon { |
31 | @include icon(21px); | 31 | width: 21px; |
32 | |||
33 | background-image: url('../../../assets/images/video/more.svg'); | ||
34 | top: -1px; | ||
35 | } | 32 | } |
36 | 33 | ||
37 | &.small { | 34 | &.small { |
diff --git a/client/src/app/shared/buttons/button.component.html b/client/src/app/shared/buttons/button.component.html index 87a8daccf..b6df67102 100644 --- a/client/src/app/shared/buttons/button.component.html +++ b/client/src/app/shared/buttons/button.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <span class="action-button" [ngClass]="className" [title]="getTitle()"> | 1 | <span class="action-button" [ngClass]="className" [title]="getTitle()"> |
2 | <span class="icon" [ngClass]="icon"></span> | 2 | <my-global-icon [iconName]="icon"></my-global-icon> |
3 | <span class="button-label">{{ label }}</span> | 3 | <span class="button-label">{{ label }}</span> |
4 | </span> | 4 | </span> |
diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/buttons/button.component.scss index 168102f09..04199a2a9 100644 --- a/client/src/app/shared/buttons/button.component.scss +++ b/client/src/app/shared/buttons/button.component.scss | |||
@@ -3,41 +3,18 @@ | |||
3 | 3 | ||
4 | .action-button { | 4 | .action-button { |
5 | @include peertube-button-link; | 5 | @include peertube-button-link; |
6 | @include button-with-icon(21px, 0, -2px); | ||
6 | 7 | ||
7 | font-size: 15px; | ||
8 | font-weight: $font-semibold; | 8 | font-weight: $font-semibold; |
9 | color: #585858; | 9 | color: $grey-foreground-color; |
10 | background-color: #E5E5E5; | 10 | background-color: $grey-background-color; |
11 | 11 | ||
12 | &:hover { | 12 | &:hover { |
13 | background-color: #EFEFEF; | 13 | background-color: $grey-background-hover-color; |
14 | } | 14 | } |
15 | 15 | ||
16 | .icon { | 16 | my-global-icon { |
17 | @include icon(21px); | 17 | @include apply-svg-color($grey-foreground-color); |
18 | |||
19 | position: relative; | ||
20 | top: -2px; | ||
21 | |||
22 | &.icon-edit { | ||
23 | background-image: url('../../../assets/images/global/edit-grey.svg'); | ||
24 | } | ||
25 | |||
26 | &.icon-delete-grey { | ||
27 | background-image: url('../../../assets/images/global/delete-grey.svg'); | ||
28 | } | ||
29 | |||
30 | &.icon-im-with-her { | ||
31 | background-image: url('../../../assets/images/global/im-with-her.svg'); | ||
32 | } | ||
33 | |||
34 | &.icon-tick { | ||
35 | background-image: url('../../../assets/images/global/tick.svg'); | ||
36 | } | ||
37 | |||
38 | &.icon-cross { | ||
39 | background-image: url('../../../assets/images/global/cross.svg'); | ||
40 | } | ||
41 | } | 18 | } |
42 | } | 19 | } |
43 | 20 | ||
diff --git a/client/src/app/shared/buttons/button.component.ts b/client/src/app/shared/buttons/button.component.ts index 1a1162f09..a91e9c7eb 100644 --- a/client/src/app/shared/buttons/button.component.ts +++ b/client/src/app/shared/buttons/button.component.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { GlobalIconName } from '@app/shared/icons/global-icon.component' | ||
2 | 3 | ||
3 | @Component({ | 4 | @Component({ |
4 | selector: 'my-button', | 5 | selector: 'my-button', |
@@ -9,7 +10,7 @@ import { Component, Input } from '@angular/core' | |||
9 | export class ButtonComponent { | 10 | export class ButtonComponent { |
10 | @Input() label = '' | 11 | @Input() label = '' |
11 | @Input() className: string = undefined | 12 | @Input() className: string = undefined |
12 | @Input() icon: string = undefined | 13 | @Input() icon: GlobalIconName = undefined |
13 | @Input() title: string = undefined | 14 | @Input() title: string = undefined |
14 | 15 | ||
15 | getTitle () { | 16 | getTitle () { |
diff --git a/client/src/app/shared/buttons/delete-button.component.html b/client/src/app/shared/buttons/delete-button.component.html index 6c55d8104..4d12a84c0 100644 --- a/client/src/app/shared/buttons/delete-button.component.html +++ b/client/src/app/shared/buttons/delete-button.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <span class="action-button action-button-delete" [title]="getTitle()" role="button"> | 1 | <span class="action-button action-button-delete" [title]="getTitle()" role="button"> |
2 | <span class="icon icon-delete-grey"></span> | 2 | <my-global-icon iconName="delete"></my-global-icon> |
3 | 3 | ||
4 | <span class="button-label" *ngIf="label">{{ label }}</span> | 4 | <span class="button-label" *ngIf="label">{{ label }}</span> |
5 | <span class="button-label" i18n *ngIf="!label">Delete</span> | 5 | <span class="button-label" i18n *ngIf="!label">Delete</span> |
diff --git a/client/src/app/shared/buttons/edit-button.component.html b/client/src/app/shared/buttons/edit-button.component.html index cecb780f3..da3addbae 100644 --- a/client/src/app/shared/buttons/edit-button.component.html +++ b/client/src/app/shared/buttons/edit-button.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <a class="action-button action-button-edit" [routerLink]="routerLink" i18n-title title="Edit"> | 1 | <a class="action-button action-button-edit" [routerLink]="routerLink" i18n-title title="Edit"> |
2 | <span class="icon icon-edit"></span> | 2 | <my-global-icon iconName="edit"></my-global-icon> |
3 | 3 | ||
4 | <span class="button-label" *ngIf="label">{{ label }}</span> | 4 | <span class="button-label" *ngIf="label">{{ label }}</span> |
5 | <span i18n class="button-label" *ngIf="!label">Edit</span> | 5 | <span i18n class="button-label" *ngIf="!label">Edit</span> |
diff --git a/client/src/app/core/confirm/confirm.component.html b/client/src/app/shared/confirm/confirm.component.html index 43f0c6190..65df1cd4d 100644 --- a/client/src/app/core/confirm/confirm.component.html +++ b/client/src/app/shared/confirm/confirm.component.html | |||
@@ -2,7 +2,8 @@ | |||
2 | 2 | ||
3 | <div class="modal-header"> | 3 | <div class="modal-header"> |
4 | <h4 class="modal-title">{{ title }}</h4> | 4 | <h4 class="modal-title">{{ title }}</h4> |
5 | <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span> | 5 | |
6 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon> | ||
6 | </div> | 7 | </div> |
7 | 8 | ||
8 | <div class="modal-body" > | 9 | <div class="modal-body" > |
diff --git a/client/src/app/core/confirm/confirm.component.scss b/client/src/app/shared/confirm/confirm.component.scss index 93dd7926b..93dd7926b 100644 --- a/client/src/app/core/confirm/confirm.component.scss +++ b/client/src/app/shared/confirm/confirm.component.scss | |||
diff --git a/client/src/app/core/confirm/confirm.component.ts b/client/src/app/shared/confirm/confirm.component.ts index 5138b7848..63c163da6 100644 --- a/client/src/app/core/confirm/confirm.component.ts +++ b/client/src/app/shared/confirm/confirm.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core' |
2 | import { ConfirmService } from './confirm.service' | 2 | import { ConfirmService } from '@app/core/confirm/confirm.service' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts index 0bb7d25e6..b9873af2c 100644 --- a/client/src/app/shared/forms/form-reactive.ts +++ b/client/src/app/shared/forms/form-reactive.ts | |||
@@ -1,11 +1,9 @@ | |||
1 | import { FormGroup } from '@angular/forms' | 1 | import { FormGroup } from '@angular/forms' |
2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
3 | 3 | ||
4 | export type FormReactiveErrors = { [ id: string ]: string } | 4 | export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } |
5 | export type FormReactiveValidationMessages = { | 5 | export type FormReactiveValidationMessages = { |
6 | [ id: string ]: { | 6 | [ id: string ]: { [ name: string ]: string } | FormReactiveValidationMessages |
7 | [ name: string ]: string | ||
8 | } | ||
9 | } | 7 | } |
10 | 8 | ||
11 | export abstract class FormReactive { | 9 | export abstract class FormReactive { |
@@ -13,7 +11,7 @@ export abstract class FormReactive { | |||
13 | protected formChanged = false | 11 | protected formChanged = false |
14 | 12 | ||
15 | form: FormGroup | 13 | form: FormGroup |
16 | formErrors: FormReactiveErrors | 14 | formErrors: any // To avoid casting in template because of string | FormReactiveErrors |
17 | validationMessages: FormReactiveValidationMessages | 15 | validationMessages: FormReactiveValidationMessages |
18 | 16 | ||
19 | buildForm (obj: BuildFormArgument, defaultValues: BuildFormDefaultValues = {}) { | 17 | buildForm (obj: BuildFormArgument, defaultValues: BuildFormDefaultValues = {}) { |
@@ -23,29 +21,49 @@ export abstract class FormReactive { | |||
23 | this.formErrors = formErrors | 21 | this.formErrors = formErrors |
24 | this.validationMessages = validationMessages | 22 | this.validationMessages = validationMessages |
25 | 23 | ||
26 | this.form.valueChanges.subscribe(() => this.onValueChanged(false)) | 24 | this.form.valueChanges.subscribe(() => this.onValueChanged(this.form, this.formErrors, this.validationMessages, false)) |
25 | } | ||
26 | |||
27 | protected forceCheck () { | ||
28 | return this.onValueChanged(this.form, this.formErrors, this.validationMessages, true) | ||
29 | } | ||
30 | |||
31 | protected check () { | ||
32 | return this.onValueChanged(this.form, this.formErrors, this.validationMessages, false) | ||
27 | } | 33 | } |
28 | 34 | ||
29 | protected onValueChanged (forceCheck = false) { | 35 | private onValueChanged ( |
30 | for (const field in this.formErrors) { | 36 | form: FormGroup, |
37 | formErrors: FormReactiveErrors, | ||
38 | validationMessages: FormReactiveValidationMessages, | ||
39 | forceCheck = false | ||
40 | ) { | ||
41 | for (const field of Object.keys(formErrors)) { | ||
42 | if (formErrors[field] && typeof formErrors[field] === 'object') { | ||
43 | this.onValueChanged( | ||
44 | form.controls[field] as FormGroup, | ||
45 | formErrors[field] as FormReactiveErrors, | ||
46 | validationMessages[field] as FormReactiveValidationMessages, | ||
47 | forceCheck | ||
48 | ) | ||
49 | continue | ||
50 | } | ||
51 | |||
31 | // clear previous error message (if any) | 52 | // clear previous error message (if any) |
32 | this.formErrors[ field ] = '' | 53 | formErrors[ field ] = '' |
33 | const control = this.form.get(field) | 54 | const control = form.get(field) |
34 | 55 | ||
35 | if (control.dirty) this.formChanged = true | 56 | if (control.dirty) this.formChanged = true |
36 | 57 | ||
37 | // Don't care if dirty on force check | 58 | // Don't care if dirty on force check |
38 | const isDirty = control.dirty || forceCheck === true | 59 | const isDirty = control.dirty || forceCheck === true |
39 | if (control && isDirty && !control.valid) { | 60 | if (control && isDirty && !control.valid) { |
40 | const messages = this.validationMessages[ field ] | 61 | const messages = validationMessages[ field ] |
41 | for (const key in control.errors) { | 62 | for (const key in control.errors) { |
42 | this.formErrors[ field ] += messages[ key ] + ' ' | 63 | formErrors[ field ] += messages[ key ] + ' ' |
43 | } | 64 | } |
44 | } | 65 | } |
45 | } | 66 | } |
46 | } | 67 | } |
47 | 68 | ||
48 | protected forceCheck () { | ||
49 | return this.onValueChanged(true) | ||
50 | } | ||
51 | } | 69 | } |
diff --git a/client/src/app/shared/forms/form-validators/form-validator.service.ts b/client/src/app/shared/forms/form-validators/form-validator.service.ts index 19a8bef25..249fdf119 100644 --- a/client/src/app/shared/forms/form-validators/form-validator.service.ts +++ b/client/src/app/shared/forms/form-validators/form-validator.service.ts | |||
@@ -7,10 +7,10 @@ export type BuildFormValidator = { | |||
7 | MESSAGES: { [ name: string ]: string } | 7 | MESSAGES: { [ name: string ]: string } |
8 | } | 8 | } |
9 | export type BuildFormArgument = { | 9 | export type BuildFormArgument = { |
10 | [ id: string ]: BuildFormValidator | 10 | [ id: string ]: BuildFormValidator | BuildFormArgument |
11 | } | 11 | } |
12 | export type BuildFormDefaultValues = { | 12 | export type BuildFormDefaultValues = { |
13 | [ name: string ]: string | string[] | 13 | [ name: string ]: string | string[] | BuildFormDefaultValues |
14 | } | 14 | } |
15 | 15 | ||
16 | @Injectable() | 16 | @Injectable() |
@@ -29,7 +29,16 @@ export class FormValidatorService { | |||
29 | formErrors[name] = '' | 29 | formErrors[name] = '' |
30 | 30 | ||
31 | const field = obj[name] | 31 | const field = obj[name] |
32 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES | 32 | if (this.isRecursiveField(field)) { |
33 | const result = this.buildForm(field as BuildFormArgument, defaultValues[name] as BuildFormDefaultValues) | ||
34 | group[name] = result.form | ||
35 | formErrors[name] = result.formErrors | ||
36 | validationMessages[name] = result.validationMessages | ||
37 | |||
38 | continue | ||
39 | } | ||
40 | |||
41 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } | ||
33 | 42 | ||
34 | const defaultValue = defaultValues[name] || '' | 43 | const defaultValue = defaultValues[name] || '' |
35 | 44 | ||
@@ -52,13 +61,27 @@ export class FormValidatorService { | |||
52 | formErrors[name] = '' | 61 | formErrors[name] = '' |
53 | 62 | ||
54 | const field = obj[name] | 63 | const field = obj[name] |
55 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES | 64 | if (this.isRecursiveField(field)) { |
65 | this.updateForm( | ||
66 | form[name], | ||
67 | formErrors[name] as FormReactiveErrors, | ||
68 | validationMessages[name] as FormReactiveValidationMessages, | ||
69 | obj[name] as BuildFormArgument, | ||
70 | defaultValues[name] as BuildFormDefaultValues | ||
71 | ) | ||
72 | continue | ||
73 | } | ||
74 | |||
75 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } | ||
56 | 76 | ||
57 | const defaultValue = defaultValues[name] || '' | 77 | const defaultValue = defaultValues[name] || '' |
58 | 78 | ||
59 | if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS)) | 79 | if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS as ValidatorFn[])) |
60 | else form.addControl(name, new FormControl(defaultValue)) | 80 | else form.addControl(name, new FormControl(defaultValue)) |
61 | } | 81 | } |
62 | } | 82 | } |
63 | 83 | ||
84 | private isRecursiveField (field: any) { | ||
85 | return field && typeof field === 'object' && !field.MESSAGES && !field.VALIDATORS | ||
86 | } | ||
64 | } | 87 | } |
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts index 74e385b3d..fdcbedb71 100644 --- a/client/src/app/shared/forms/form-validators/index.ts +++ b/client/src/app/shared/forms/form-validators/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './custom-config-validators.service' | 1 | export * from './custom-config-validators.service' |
2 | export * from './form-validator.service' | 2 | export * from './form-validator.service' |
3 | export * from './host' | 3 | export * from './host' |
4 | export * from './instance-validators.service' | ||
4 | export * from './login-validators.service' | 5 | export * from './login-validators.service' |
5 | export * from './reset-password-validators.service' | 6 | export * from './reset-password-validators.service' |
6 | export * from './user-validators.service' | 7 | export * from './user-validators.service' |
diff --git a/client/src/app/shared/forms/form-validators/instance-validators.service.ts b/client/src/app/shared/forms/form-validators/instance-validators.service.ts new file mode 100644 index 000000000..5bb852858 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/instance-validators.service.ts | |||
@@ -0,0 +1,48 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from '@app/shared' | ||
4 | import { Injectable } from '@angular/core' | ||
5 | |||
6 | @Injectable() | ||
7 | export class InstanceValidatorsService { | ||
8 | readonly FROM_EMAIL: BuildFormValidator | ||
9 | readonly FROM_NAME: BuildFormValidator | ||
10 | readonly BODY: BuildFormValidator | ||
11 | |||
12 | constructor (private i18n: I18n) { | ||
13 | |||
14 | this.FROM_EMAIL = { | ||
15 | VALIDATORS: [ Validators.required, Validators.email ], | ||
16 | MESSAGES: { | ||
17 | 'required': this.i18n('Email is required.'), | ||
18 | 'email': this.i18n('Email must be valid.') | ||
19 | } | ||
20 | } | ||
21 | |||
22 | this.FROM_NAME = { | ||
23 | VALIDATORS: [ | ||
24 | Validators.required, | ||
25 | Validators.minLength(1), | ||
26 | Validators.maxLength(120) | ||
27 | ], | ||
28 | MESSAGES: { | ||
29 | 'required': this.i18n('Your name is required.'), | ||
30 | 'minlength': this.i18n('Your name must be at least 1 character long.'), | ||
31 | 'maxlength': this.i18n('Your name cannot be more than 120 characters long.') | ||
32 | } | ||
33 | } | ||
34 | |||
35 | this.BODY = { | ||
36 | VALIDATORS: [ | ||
37 | Validators.required, | ||
38 | Validators.minLength(3), | ||
39 | Validators.maxLength(5000) | ||
40 | ], | ||
41 | MESSAGES: { | ||
42 | 'required': this.i18n('A message is required.'), | ||
43 | 'minlength': this.i18n('The message must be at least 3 characters long.'), | ||
44 | 'maxlength': this.i18n('The message cannot be more than 5000 characters long.') | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | } | ||
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/forms/form-validators/user-validators.service.ts index d14fa4777..6589b2580 100644 --- a/client/src/app/shared/forms/form-validators/user-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/user-validators.service.ts | |||
@@ -23,15 +23,15 @@ export class UserValidatorsService { | |||
23 | this.USER_USERNAME = { | 23 | this.USER_USERNAME = { |
24 | VALIDATORS: [ | 24 | VALIDATORS: [ |
25 | Validators.required, | 25 | Validators.required, |
26 | Validators.minLength(3), | 26 | Validators.minLength(1), |
27 | Validators.maxLength(20), | 27 | Validators.maxLength(50), |
28 | Validators.pattern(/^[a-z0-9._]+$/) | 28 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) |
29 | ], | 29 | ], |
30 | MESSAGES: { | 30 | MESSAGES: { |
31 | 'required': this.i18n('Username is required.'), | 31 | 'required': this.i18n('Username is required.'), |
32 | 'minlength': this.i18n('Username must be at least 3 characters long.'), | 32 | 'minlength': this.i18n('Username must be at least 1 character long.'), |
33 | 'maxlength': this.i18n('Username cannot be more than 20 characters long.'), | 33 | 'maxlength': this.i18n('Username cannot be more than 50 characters long.'), |
34 | 'pattern': this.i18n('Username should be only lowercase alphanumeric characters.') | 34 | 'pattern': this.i18n('Username should be lowercase alphanumeric; dots and underscores are allowed.') |
35 | } | 35 | } |
36 | } | 36 | } |
37 | 37 | ||
@@ -88,13 +88,13 @@ export class UserValidatorsService { | |||
88 | this.USER_DISPLAY_NAME = { | 88 | this.USER_DISPLAY_NAME = { |
89 | VALIDATORS: [ | 89 | VALIDATORS: [ |
90 | Validators.required, | 90 | Validators.required, |
91 | Validators.minLength(3), | 91 | Validators.minLength(1), |
92 | Validators.maxLength(120) | 92 | Validators.maxLength(50) |
93 | ], | 93 | ], |
94 | MESSAGES: { | 94 | MESSAGES: { |
95 | 'required': this.i18n('Display name is required.'), | 95 | 'required': this.i18n('Display name is required.'), |
96 | 'minlength': this.i18n('Display name must be at least 3 characters long.'), | 96 | 'minlength': this.i18n('Display name must be at least 1 character long.'), |
97 | 'maxlength': this.i18n('Display name cannot be more than 120 characters long.') | 97 | 'maxlength': this.i18n('Display name cannot be more than 50 characters long.') |
98 | } | 98 | } |
99 | } | 99 | } |
100 | 100 | ||
diff --git a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts b/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts index 6e9806611..fcc966b84 100644 --- a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts | |||
@@ -10,20 +10,20 @@ export class VideoAbuseValidatorsService { | |||
10 | 10 | ||
11 | constructor (private i18n: I18n) { | 11 | constructor (private i18n: I18n) { |
12 | this.VIDEO_ABUSE_REASON = { | 12 | this.VIDEO_ABUSE_REASON = { |
13 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ], | 13 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], |
14 | MESSAGES: { | 14 | MESSAGES: { |
15 | 'required': this.i18n('Report reason is required.'), | 15 | 'required': this.i18n('Report reason is required.'), |
16 | 'minlength': this.i18n('Report reason must be at least 2 characters long.'), | 16 | 'minlength': this.i18n('Report reason must be at least 2 characters long.'), |
17 | 'maxlength': this.i18n('Report reason cannot be more than 300 characters long.') | 17 | 'maxlength': this.i18n('Report reason cannot be more than 3000 characters long.') |
18 | } | 18 | } |
19 | } | 19 | } |
20 | 20 | ||
21 | this.VIDEO_ABUSE_MODERATION_COMMENT = { | 21 | this.VIDEO_ABUSE_MODERATION_COMMENT = { |
22 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ], | 22 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], |
23 | MESSAGES: { | 23 | MESSAGES: { |
24 | 'required': this.i18n('Moderation comment is required.'), | 24 | 'required': this.i18n('Moderation comment is required.'), |
25 | 'minlength': this.i18n('Moderation comment must be at least 2 characters long.'), | 25 | 'minlength': this.i18n('Moderation comment must be at least 2 characters long.'), |
26 | 'maxlength': this.i18n('Moderation comment cannot be more than 300 characters long.') | 26 | 'maxlength': this.i18n('Moderation comment cannot be more than 3000 characters long.') |
27 | } | 27 | } |
28 | } | 28 | } |
29 | } | 29 | } |
diff --git a/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts b/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts index f62ff65f7..1c519c10a 100644 --- a/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts | |||
@@ -14,28 +14,28 @@ export class VideoChannelValidatorsService { | |||
14 | this.VIDEO_CHANNEL_NAME = { | 14 | this.VIDEO_CHANNEL_NAME = { |
15 | VALIDATORS: [ | 15 | VALIDATORS: [ |
16 | Validators.required, | 16 | Validators.required, |
17 | Validators.minLength(3), | 17 | Validators.minLength(1), |
18 | Validators.maxLength(20), | 18 | Validators.maxLength(50), |
19 | Validators.pattern(/^[a-z0-9._]+$/) | 19 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) |
20 | ], | 20 | ], |
21 | MESSAGES: { | 21 | MESSAGES: { |
22 | 'required': this.i18n('Name is required.'), | 22 | 'required': this.i18n('Name is required.'), |
23 | 'minlength': this.i18n('Name must be at least 3 characters long.'), | 23 | 'minlength': this.i18n('Name must be at least 1 character long.'), |
24 | 'maxlength': this.i18n('Name cannot be more than 20 characters long.'), | 24 | 'maxlength': this.i18n('Name cannot be more than 50 characters long.'), |
25 | 'pattern': this.i18n('Name should be only lowercase alphanumeric characters.') | 25 | 'pattern': this.i18n('Name should be lowercase alphanumeric; dots and underscores are allowed.') |
26 | } | 26 | } |
27 | } | 27 | } |
28 | 28 | ||
29 | this.VIDEO_CHANNEL_DISPLAY_NAME = { | 29 | this.VIDEO_CHANNEL_DISPLAY_NAME = { |
30 | VALIDATORS: [ | 30 | VALIDATORS: [ |
31 | Validators.required, | 31 | Validators.required, |
32 | Validators.minLength(3), | 32 | Validators.minLength(1), |
33 | Validators.maxLength(120) | 33 | Validators.maxLength(50) |
34 | ], | 34 | ], |
35 | MESSAGES: { | 35 | MESSAGES: { |
36 | 'required': i18n('Display name is required.'), | 36 | 'required': i18n('Display name is required.'), |
37 | 'minlength': i18n('Display name must be at least 3 characters long.'), | 37 | 'minlength': i18n('Display name must be at least 1 character long.'), |
38 | 'maxlength': i18n('Display name cannot be more than 120 characters long.') | 38 | 'maxlength': i18n('Display name cannot be more than 50 characters long.') |
39 | } | 39 | } |
40 | } | 40 | } |
41 | 41 | ||
diff --git a/client/src/app/shared/forms/markdown-textarea.component.ts b/client/src/app/shared/forms/markdown-textarea.component.ts index b99169ed2..e87aca0d4 100644 --- a/client/src/app/shared/forms/markdown-textarea.component.ts +++ b/client/src/app/shared/forms/markdown-textarea.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | 1 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' |
2 | import { Component, forwardRef, Input, OnInit } from '@angular/core' | 2 | import { Component, forwardRef, Input, OnInit } from '@angular/core' |
3 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 3 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
4 | import { MarkdownService } from '@app/videos/shared' | ||
5 | import { Subject } from 'rxjs' | 4 | import { Subject } from 'rxjs' |
6 | import truncate from 'lodash-es/truncate' | 5 | import truncate from 'lodash-es/truncate' |
7 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { MarkdownService } from '@app/shared/renderer' | ||
8 | 8 | ||
9 | @Component({ | 9 | @Component({ |
10 | selector: 'my-markdown-textarea', | 10 | selector: 'my-markdown-textarea', |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/forms/peertube-checkbox.component.html index fb3006b53..7b8bcf601 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.html +++ b/client/src/app/shared/forms/peertube-checkbox.component.html | |||
@@ -1,10 +1,10 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | <label class="form-group-checkbox"> | 2 | <label class="form-group-checkbox"> |
3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="isDisabled" /> | 3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> |
4 | <span role="checkbox" [attr.aria-checked]="checked"></span> | 4 | <span role="checkbox" [attr.aria-checked]="checked"></span> |
5 | <span *ngIf="labelText">{{ labelText }}</span> | 5 | <span *ngIf="labelText">{{ labelText }}</span> |
6 | <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> | 6 | <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> |
7 | </label> | 7 | </label> |
8 | 8 | ||
9 | <my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> | 9 | <my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> |
10 | </div> \ No newline at end of file | 10 | </div> |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/forms/peertube-checkbox.component.ts index bbc9904df..c1a6915e8 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.ts +++ b/client/src/app/shared/forms/peertube-checkbox.component.ts | |||
@@ -19,8 +19,7 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor { | |||
19 | @Input() labelText: string | 19 | @Input() labelText: string |
20 | @Input() labelHtml: string | 20 | @Input() labelHtml: string |
21 | @Input() helpHtml: string | 21 | @Input() helpHtml: string |
22 | 22 | @Input() disabled = false | |
23 | isDisabled = false | ||
24 | 23 | ||
25 | propagateChange = (_: any) => { /* empty */ } | 24 | propagateChange = (_: any) => { /* empty */ } |
26 | 25 | ||
@@ -41,6 +40,6 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor { | |||
41 | } | 40 | } |
42 | 41 | ||
43 | setDisabledState (isDisabled: boolean) { | 42 | setDisabledState (isDisabled: boolean) { |
44 | this.isDisabled = isDisabled | 43 | this.disabled = isDisabled |
45 | } | 44 | } |
46 | } | 45 | } |
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/forms/reactive-file.component.ts index 8d22aa56c..f60c38e8d 100644 --- a/client/src/app/shared/forms/reactive-file.component.ts +++ b/client/src/app/shared/forms/reactive-file.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | 5 | ||
6 | @Component({ | 6 | @Component({ |
@@ -30,7 +30,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor { | |||
30 | private file: File | 30 | private file: File |
31 | 31 | ||
32 | constructor ( | 32 | constructor ( |
33 | private notificationsService: NotificationsService, | 33 | private notifier: Notifier, |
34 | private i18n: I18n | 34 | private i18n: I18n |
35 | ) {} | 35 | ) {} |
36 | 36 | ||
@@ -49,7 +49,18 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor { | |||
49 | const [ file ] = event.target.files | 49 | const [ file ] = event.target.files |
50 | 50 | ||
51 | if (file.size > this.maxFileSize) { | 51 | if (file.size > this.maxFileSize) { |
52 | this.notificationsService.error(this.i18n('Error'), this.i18n('This file is too large.')) | 52 | this.notifier.error(this.i18n('This file is too large.')) |
53 | return | ||
54 | } | ||
55 | |||
56 | const extension = '.' + file.name.split('.').pop() | ||
57 | if (this.extensions.includes(extension) === false) { | ||
58 | const message = this.i18n( | ||
59 | 'PeerTube cannot handle this kind of file. Accepted extensions are {{extensions}}.', | ||
60 | { extensions: this.allowedExtensionsMessage } | ||
61 | ) | ||
62 | this.notifier.error(message) | ||
63 | |||
53 | return | 64 | return |
54 | } | 65 | } |
55 | 66 | ||
diff --git a/client/src/app/shared/icons/global-icon.component.html b/client/src/app/shared/icons/global-icon.component.html new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/client/src/app/shared/icons/global-icon.component.html | |||
diff --git a/client/src/app/shared/icons/global-icon.component.scss b/client/src/app/shared/icons/global-icon.component.scss new file mode 100644 index 000000000..6805fb6f7 --- /dev/null +++ b/client/src/app/shared/icons/global-icon.component.scss | |||
@@ -0,0 +1,4 @@ | |||
1 | /deep/ svg { | ||
2 | width: inherit; | ||
3 | height: inherit; | ||
4 | } | ||
diff --git a/client/src/app/shared/icons/global-icon.component.ts b/client/src/app/shared/icons/global-icon.component.ts new file mode 100644 index 000000000..e8ada0324 --- /dev/null +++ b/client/src/app/shared/icons/global-icon.component.ts | |||
@@ -0,0 +1,48 @@ | |||
1 | import { Component, ElementRef, Input, OnInit } from '@angular/core' | ||
2 | |||
3 | const icons = { | ||
4 | 'add': require('../../../assets/images/global/add.html'), | ||
5 | 'syndication': require('../../../assets/images/global/syndication.html'), | ||
6 | 'help': require('../../../assets/images/global/help.html'), | ||
7 | 'sparkle': require('../../../assets/images/global/sparkle.html'), | ||
8 | 'alert': require('../../../assets/images/global/alert.html'), | ||
9 | 'cloud-error': require('../../../assets/images/global/cloud-error.html'), | ||
10 | 'user-add': require('../../../assets/images/global/user-add.html'), | ||
11 | 'no': require('../../../assets/images/global/no.html'), | ||
12 | 'cloud-download': require('../../../assets/images/global/cloud-download.html'), | ||
13 | 'undo': require('../../../assets/images/global/undo.html'), | ||
14 | 'circle-tick': require('../../../assets/images/global/circle-tick.html'), | ||
15 | 'cog': require('../../../assets/images/global/cog.html'), | ||
16 | 'download': require('../../../assets/images/global/download.html'), | ||
17 | 'edit': require('../../../assets/images/global/edit.html'), | ||
18 | 'im-with-her': require('../../../assets/images/global/im-with-her.html'), | ||
19 | 'delete': require('../../../assets/images/global/delete.html'), | ||
20 | 'cross': require('../../../assets/images/global/cross.html'), | ||
21 | 'validate': require('../../../assets/images/global/validate.html'), | ||
22 | 'tick': require('../../../assets/images/global/tick.html'), | ||
23 | 'dislike': require('../../../assets/images/video/dislike.html'), | ||
24 | 'heart': require('../../../assets/images/video/heart.html'), | ||
25 | 'like': require('../../../assets/images/video/like.html'), | ||
26 | 'more': require('../../../assets/images/video/more.html'), | ||
27 | 'share': require('../../../assets/images/video/share.html'), | ||
28 | 'upload': require('../../../assets/images/video/upload.html') | ||
29 | } | ||
30 | |||
31 | export type GlobalIconName = keyof typeof icons | ||
32 | |||
33 | @Component({ | ||
34 | selector: 'my-global-icon', | ||
35 | template: '', | ||
36 | styleUrls: [ './global-icon.component.scss' ] | ||
37 | }) | ||
38 | export class GlobalIconComponent implements OnInit { | ||
39 | @Input() iconName: GlobalIconName | ||
40 | |||
41 | constructor (private el: ElementRef) {} | ||
42 | |||
43 | ngOnInit () { | ||
44 | const nativeElement = this.el.nativeElement | ||
45 | |||
46 | nativeElement.innerHTML = icons[this.iconName] | ||
47 | } | ||
48 | } | ||
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts new file mode 100644 index 000000000..61321ecce --- /dev/null +++ b/client/src/app/shared/instance/instance.service.ts | |||
@@ -0,0 +1,36 @@ | |||
1 | import { catchError } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { environment } from '../../../environments/environment' | ||
5 | import { RestExtractor, RestService } from '../rest' | ||
6 | import { About } from '../../../../../shared/models/server' | ||
7 | |||
8 | @Injectable() | ||
9 | export class InstanceService { | ||
10 | private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config' | ||
11 | private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server' | ||
12 | |||
13 | constructor ( | ||
14 | private authHttp: HttpClient, | ||
15 | private restService: RestService, | ||
16 | private restExtractor: RestExtractor | ||
17 | ) { | ||
18 | } | ||
19 | |||
20 | getAbout () { | ||
21 | return this.authHttp.get<About>(InstanceService.BASE_CONFIG_URL + '/about') | ||
22 | .pipe(catchError(res => this.restExtractor.handleError(res))) | ||
23 | } | ||
24 | |||
25 | contactAdministrator (fromEmail: string, fromName: string, message: string) { | ||
26 | const body = { | ||
27 | fromEmail, | ||
28 | fromName, | ||
29 | body: message | ||
30 | } | ||
31 | |||
32 | return this.authHttp.post(InstanceService.BASE_SERVER_URL + '/contact', body) | ||
33 | .pipe(catchError(res => this.restExtractor.handleError(res))) | ||
34 | |||
35 | } | ||
36 | } | ||
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.html b/client/src/app/shared/menu/top-menu-dropdown.component.html new file mode 100644 index 000000000..d3c896019 --- /dev/null +++ b/client/src/app/shared/menu/top-menu-dropdown.component.html | |||
@@ -0,0 +1,21 @@ | |||
1 | <div class="sub-menu"> | ||
2 | <ng-container *ngFor="let menuEntry of menuEntries"> | ||
3 | |||
4 | <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page">{{ menuEntry.label }}</a> | ||
5 | |||
6 | <div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)"> | ||
7 | <span | ||
8 | (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor | ||
9 | (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page" | ||
10 | > | ||
11 | <ng-container i18n>{{ menuEntry.label }}</ng-container> | ||
12 | <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container> | ||
13 | </span> | ||
14 | |||
15 | <div ngbDropdownMenu> | ||
16 | <a *ngFor="let menuChild of menuEntry.children" class="dropdown-item" [routerLink]="menuChild.routerLink">{{ menuChild.label }}</a> | ||
17 | </div> | ||
18 | </div> | ||
19 | |||
20 | </ng-container> | ||
21 | </div> | ||
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.scss b/client/src/app/shared/menu/top-menu-dropdown.component.scss new file mode 100644 index 000000000..77159532f --- /dev/null +++ b/client/src/app/shared/menu/top-menu-dropdown.component.scss | |||
@@ -0,0 +1,18 @@ | |||
1 | .parent-entry { | ||
2 | span[role=button] { | ||
3 | cursor: pointer; | ||
4 | } | ||
5 | |||
6 | a { | ||
7 | display: block; | ||
8 | } | ||
9 | } | ||
10 | |||
11 | /deep/ .dropdown-toggle::after { | ||
12 | position: relative; | ||
13 | top: 2px; | ||
14 | } | ||
15 | |||
16 | /deep/ .dropdown-menu { | ||
17 | margin-top: 0 !important; | ||
18 | } | ||
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts new file mode 100644 index 000000000..e859c30dd --- /dev/null +++ b/client/src/app/shared/menu/top-menu-dropdown.component.ts | |||
@@ -0,0 +1,83 @@ | |||
1 | import { Component, Input, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { filter, take } from 'rxjs/operators' | ||
3 | import { NavigationEnd, Router } from '@angular/router' | ||
4 | import { Subscription } from 'rxjs' | ||
5 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | ||
6 | |||
7 | export type TopMenuDropdownParam = { | ||
8 | label: string | ||
9 | routerLink?: string | ||
10 | |||
11 | children?: { | ||
12 | label: string | ||
13 | routerLink: string | ||
14 | }[] | ||
15 | } | ||
16 | |||
17 | @Component({ | ||
18 | selector: 'my-top-menu-dropdown', | ||
19 | templateUrl: './top-menu-dropdown.component.html', | ||
20 | styleUrls: [ './top-menu-dropdown.component.scss' ] | ||
21 | }) | ||
22 | export class TopMenuDropdownComponent implements OnInit, OnDestroy { | ||
23 | @Input() menuEntries: TopMenuDropdownParam[] = [] | ||
24 | |||
25 | suffixLabels: { [ parentLabel: string ]: string } | ||
26 | |||
27 | private openedOnHover = false | ||
28 | private routeSub: Subscription | ||
29 | |||
30 | constructor (private router: Router) {} | ||
31 | |||
32 | ngOnInit () { | ||
33 | this.updateChildLabels(window.location.pathname) | ||
34 | |||
35 | this.routeSub = this.router.events | ||
36 | .pipe(filter(event => event instanceof NavigationEnd)) | ||
37 | .subscribe(() => this.updateChildLabels(window.location.pathname)) | ||
38 | } | ||
39 | |||
40 | ngOnDestroy () { | ||
41 | if (this.routeSub) this.routeSub.unsubscribe() | ||
42 | } | ||
43 | |||
44 | openDropdownOnHover (dropdown: NgbDropdown) { | ||
45 | this.openedOnHover = true | ||
46 | dropdown.open() | ||
47 | |||
48 | // Menu was closed | ||
49 | dropdown.openChange | ||
50 | .pipe(take(1)) | ||
51 | .subscribe(e => this.openedOnHover = false) | ||
52 | } | ||
53 | |||
54 | dropdownAnchorClicked (dropdown: NgbDropdown) { | ||
55 | if (this.openedOnHover) { | ||
56 | this.openedOnHover = false | ||
57 | return | ||
58 | } | ||
59 | |||
60 | return dropdown.toggle() | ||
61 | } | ||
62 | |||
63 | closeDropdownIfHovered (dropdown: NgbDropdown) { | ||
64 | if (this.openedOnHover === false) return | ||
65 | |||
66 | dropdown.close() | ||
67 | this.openedOnHover = false | ||
68 | } | ||
69 | |||
70 | private updateChildLabels (path: string) { | ||
71 | this.suffixLabels = {} | ||
72 | |||
73 | for (const entry of this.menuEntries) { | ||
74 | if (!entry.children) continue | ||
75 | |||
76 | for (const child of entry.children) { | ||
77 | if (path.startsWith(child.routerLink)) { | ||
78 | this.suffixLabels[entry.label] = child.label | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | } | ||
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html index 28ccb1e26..444425c9f 100644 --- a/client/src/app/shared/misc/help.component.html +++ b/client/src/app/shared/misc/help.component.html | |||
@@ -18,10 +18,13 @@ | |||
18 | container="body" | 18 | container="body" |
19 | title="Get help" | 19 | title="Get help" |
20 | i18n-title | 20 | i18n-title |
21 | popoverClass="help-popover" | ||
21 | [attr.aria-pressed]="isPopoverOpened" | 22 | [attr.aria-pressed]="isPopoverOpened" |
22 | [ngbPopover]="tooltipTemplate" | 23 | [ngbPopover]="tooltipTemplate" |
23 | [placement]="tooltipPlacement" | 24 | [placement]="tooltipPlacement" |
24 | [autoClose]="true" | 25 | [autoClose]="true" |
25 | (onHidden)="onPopoverHidden()" | 26 | (onHidden)="onPopoverHidden()" |
26 | (onShown)="onPopoverShown()" | 27 | (onShown)="onPopoverShown()" |
27 | ></span> | 28 | > |
29 | <my-global-icon iconName="help"></my-global-icon> | ||
30 | </span> | ||
diff --git a/client/src/app/shared/misc/help.component.scss b/client/src/app/shared/misc/help.component.scss index 5c73a8031..3898f3cda 100644 --- a/client/src/app/shared/misc/help.component.scss +++ b/client/src/app/shared/misc/help.component.scss | |||
@@ -2,29 +2,40 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .help-tooltip-button { | 4 | .help-tooltip-button { |
5 | @include icon(17px); | 5 | cursor: pointer; |
6 | |||
7 | position: relative; | ||
8 | top: -2px; | ||
9 | background-image: url('../../../assets/images/global/help.svg'); | ||
10 | border: none; | 6 | border: none; |
11 | margin: 5px; | 7 | |
8 | my-global-icon { | ||
9 | width: 17px; | ||
10 | position: relative; | ||
11 | top: -2px; | ||
12 | margin: 5px; | ||
13 | |||
14 | @include apply-svg-color(var(--mainForegroundColor)) | ||
15 | } | ||
12 | } | 16 | } |
13 | 17 | ||
14 | /deep/ { | 18 | /deep/ { |
15 | .popover-body { | 19 | .help-popover { |
16 | text-align: left; | ||
17 | padding: 10px; | ||
18 | max-width: 300px; | 20 | max-width: 300px; |
19 | 21 | ||
20 | font-size: 13px; | 22 | .popover-body { |
21 | font-family: $main-fonts; | 23 | font-family: $main-fonts; |
22 | background-color: #fff; | 24 | text-align: left; |
23 | color: #000; | 25 | padding: 10px; |
24 | box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); | 26 | font-size: 13px; |
27 | background-color: var(--mainBackgroundColor); | ||
28 | color: var(--mainForegroundColor); | ||
29 | box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); | ||
30 | |||
31 | p { | ||
32 | margin-bottom: 0; | ||
33 | } | ||
25 | 34 | ||
26 | ul { | 35 | ul { |
27 | padding-left: 20px; | 36 | padding-left: 20px; |
37 | margin-bottom: 0; | ||
38 | } | ||
28 | } | 39 | } |
29 | } | 40 | } |
30 | } | 41 | } |
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/misc/help.component.ts index ba0452e77..f3426f70f 100644 --- a/client/src/app/shared/misc/help.component.ts +++ b/client/src/app/shared/misc/help.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, Input, OnChanges, OnInit } from '@angular/core' | 1 | import { Component, Input, OnChanges, OnInit } from '@angular/core' |
2 | import { MarkdownService } from '@app/videos/shared' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { MarkdownService } from '@app/shared/renderer' | ||
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-help', | 6 | selector: 'my-help', |
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts index 78e8e9682..7cc6055c2 100644 --- a/client/src/app/shared/misc/utils.ts +++ b/client/src/app/shared/misc/utils.ts | |||
@@ -102,12 +102,18 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) { | |||
102 | return fd | 102 | return fd |
103 | } | 103 | } |
104 | 104 | ||
105 | function lineFeedToHtml (obj: any, keyToNormalize: string) { | 105 | function objectLineFeedToHtml (obj: any, keyToNormalize: string) { |
106 | return immutableAssign(obj, { | 106 | return immutableAssign(obj, { |
107 | [keyToNormalize]: obj[keyToNormalize].replace(/\r?\n|\r/g, '<br />') | 107 | [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize]) |
108 | }) | 108 | }) |
109 | } | 109 | } |
110 | 110 | ||
111 | function lineFeedToHtml (text: string) { | ||
112 | if (!text) return text | ||
113 | |||
114 | return text.replace(/\r?\n|\r/g, '<br />') | ||
115 | } | ||
116 | |||
111 | function removeElementFromArray <T> (arr: T[], elem: T) { | 117 | function removeElementFromArray <T> (arr: T[], elem: T) { |
112 | const index = arr.indexOf(elem) | 118 | const index = arr.indexOf(elem) |
113 | if (index !== -1) arr.splice(index, 1) | 119 | if (index !== -1) arr.splice(index, 1) |
@@ -131,6 +137,7 @@ function scrollToTop () { | |||
131 | export { | 137 | export { |
132 | sortBy, | 138 | sortBy, |
133 | durationToString, | 139 | durationToString, |
140 | lineFeedToHtml, | ||
134 | objectToUrlEncoded, | 141 | objectToUrlEncoded, |
135 | getParameterByName, | 142 | getParameterByName, |
136 | populateAsyncUserVideoChannels, | 143 | populateAsyncUserVideoChannels, |
@@ -138,7 +145,7 @@ export { | |||
138 | dateToHuman, | 145 | dateToHuman, |
139 | immutableAssign, | 146 | immutableAssign, |
140 | objectToFormData, | 147 | objectToFormData, |
141 | lineFeedToHtml, | 148 | objectLineFeedToHtml, |
142 | removeElementFromArray, | 149 | removeElementFromArray, |
143 | scrollToTop | 150 | scrollToTop |
144 | } | 151 | } |
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.html b/client/src/app/shared/moderation/user-ban-modal.component.html index fa5cb7404..f38ea543d 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.html +++ b/client/src/app/shared/moderation/user-ban-modal.component.html | |||
@@ -1,7 +1,8 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Ban</h4> | 3 | <h4 i18n class="modal-title">Ban</h4> |
4 | <span class="close" aria-hidden="true" (click)="hideBanUserModal()"></span> | 4 | |
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | 6 | </div> |
6 | 7 | ||
7 | <div class="modal-body"> | 8 | <div class="modal-body"> |
@@ -19,7 +20,7 @@ | |||
19 | </div> | 20 | </div> |
20 | 21 | ||
21 | <div class="form-group inputs"> | 22 | <div class="form-group inputs"> |
22 | <span i18n class="action-button action-button-cancel" (click)="hideBanUserModal()">Cancel</span> | 23 | <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span> |
23 | 24 | ||
24 | <input | 25 | <input |
25 | type="submit" i18n-value value="Ban this user" class="action-button-submit" | 26 | type="submit" i18n-value value="Ban this user" class="action-button-submit" |
@@ -29,4 +30,4 @@ | |||
29 | </form> | 30 | </form> |
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | </ng-template> \ No newline at end of file | 33 | </ng-template> |
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/moderation/user-ban-modal.component.ts index 60bd442dd..942765301 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/moderation/user-ban-modal.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
@@ -23,7 +23,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
23 | constructor ( | 23 | constructor ( |
24 | protected formValidatorService: FormValidatorService, | 24 | protected formValidatorService: FormValidatorService, |
25 | private modalService: NgbModal, | 25 | private modalService: NgbModal, |
26 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
27 | private userService: UserService, | 27 | private userService: UserService, |
28 | private userValidatorsService: UserValidatorsService, | 28 | private userValidatorsService: UserValidatorsService, |
29 | private i18n: I18n | 29 | private i18n: I18n |
@@ -42,7 +42,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
42 | this.openedModal = this.modalService.open(this.modal) | 42 | this.openedModal = this.modalService.open(this.modal) |
43 | } | 43 | } |
44 | 44 | ||
45 | hideBanUserModal () { | 45 | hide () { |
46 | this.usersToBan = undefined | 46 | this.usersToBan = undefined |
47 | this.openedModal.close() | 47 | this.openedModal.close() |
48 | } | 48 | } |
@@ -57,13 +57,13 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
57 | ? this.i18n('{{num}} users banned.', { num: this.usersToBan.length }) | 57 | ? this.i18n('{{num}} users banned.', { num: this.usersToBan.length }) |
58 | : this.i18n('User {{username}} banned.', { username: this.usersToBan.username }) | 58 | : this.i18n('User {{username}} banned.', { username: this.usersToBan.username }) |
59 | 59 | ||
60 | this.notificationsService.success(this.i18n('Success'), message) | 60 | this.notifier.success(message) |
61 | 61 | ||
62 | this.userBanned.emit(this.usersToBan) | 62 | this.userBanned.emit(this.usersToBan) |
63 | this.hideBanUserModal() | 63 | this.hide() |
64 | }, | 64 | }, |
65 | 65 | ||
66 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 66 | err => this.notifier.error(err.message) |
67 | ) | 67 | ) |
68 | } | 68 | } |
69 | 69 | ||
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index d391246e0..9a2461ebf 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | 3 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' |
5 | import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' | 4 | import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' |
6 | import { UserService } from '@app/shared/users' | 5 | import { UserService } from '@app/shared/users' |
7 | import { AuthService, ConfirmService, ServerService } from '@app/core' | 6 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' |
8 | import { User, UserRight } from '../../../../../shared/models/users' | 7 | import { User, UserRight } from '../../../../../shared/models/users' |
9 | import { Account } from '@app/shared/account/account.model' | 8 | import { Account } from '@app/shared/account/account.model' |
10 | import { BlocklistService } from '@app/shared/blocklist' | 9 | import { BlocklistService } from '@app/shared/blocklist' |
@@ -30,7 +29,7 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
30 | 29 | ||
31 | constructor ( | 30 | constructor ( |
32 | private authService: AuthService, | 31 | private authService: AuthService, |
33 | private notificationsService: NotificationsService, | 32 | private notifier: Notifier, |
34 | private confirmService: ConfirmService, | 33 | private confirmService: ConfirmService, |
35 | private serverService: ServerService, | 34 | private serverService: ServerService, |
36 | private userService: UserService, | 35 | private userService: UserService, |
@@ -48,7 +47,7 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
48 | 47 | ||
49 | openBanUserModal (user: User) { | 48 | openBanUserModal (user: User) { |
50 | if (user.username === 'root') { | 49 | if (user.username === 'root') { |
51 | this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.')) | 50 | this.notifier.error(this.i18n('You cannot ban root.')) |
52 | return | 51 | return |
53 | } | 52 | } |
54 | 53 | ||
@@ -67,21 +66,18 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
67 | this.userService.unbanUsers(user) | 66 | this.userService.unbanUsers(user) |
68 | .subscribe( | 67 | .subscribe( |
69 | () => { | 68 | () => { |
70 | this.notificationsService.success( | 69 | this.notifier.success(this.i18n('User {{username}} unbanned.', { username: user.username })) |
71 | this.i18n('Success'), | ||
72 | this.i18n('User {{username}} unbanned.', { username: user.username }) | ||
73 | ) | ||
74 | 70 | ||
75 | this.userChanged.emit() | 71 | this.userChanged.emit() |
76 | }, | 72 | }, |
77 | 73 | ||
78 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 74 | err => this.notifier.error(err.message) |
79 | ) | 75 | ) |
80 | } | 76 | } |
81 | 77 | ||
82 | async removeUser (user: User) { | 78 | async removeUser (user: User) { |
83 | if (user.username === 'root') { | 79 | if (user.username === 'root') { |
84 | this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.')) | 80 | this.notifier.error(this.i18n('You cannot delete root.')) |
85 | return | 81 | return |
86 | } | 82 | } |
87 | 83 | ||
@@ -91,29 +87,23 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
91 | 87 | ||
92 | this.userService.removeUser(user).subscribe( | 88 | this.userService.removeUser(user).subscribe( |
93 | () => { | 89 | () => { |
94 | this.notificationsService.success( | 90 | this.notifier.success(this.i18n('User {{username}} deleted.', { username: user.username })) |
95 | this.i18n('Success'), | ||
96 | this.i18n('User {{username}} deleted.', { username: user.username }) | ||
97 | ) | ||
98 | this.userDeleted.emit() | 91 | this.userDeleted.emit() |
99 | }, | 92 | }, |
100 | 93 | ||
101 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 94 | err => this.notifier.error(err.message) |
102 | ) | 95 | ) |
103 | } | 96 | } |
104 | 97 | ||
105 | setEmailAsVerified (user: User) { | 98 | setEmailAsVerified (user: User) { |
106 | this.userService.updateUser(user.id, { emailVerified: true }).subscribe( | 99 | this.userService.updateUser(user.id, { emailVerified: true }).subscribe( |
107 | () => { | 100 | () => { |
108 | this.notificationsService.success( | 101 | this.notifier.success(this.i18n('User {{username}} email set as verified', { username: user.username })) |
109 | this.i18n('Success'), | ||
110 | this.i18n('User {{username}} email set as verified', { username: user.username }) | ||
111 | ) | ||
112 | 102 | ||
113 | this.userChanged.emit() | 103 | this.userChanged.emit() |
114 | }, | 104 | }, |
115 | 105 | ||
116 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 106 | err => this.notifier.error(err.message) |
117 | ) | 107 | ) |
118 | } | 108 | } |
119 | 109 | ||
@@ -121,16 +111,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
121 | this.blocklistService.blockAccountByUser(account) | 111 | this.blocklistService.blockAccountByUser(account) |
122 | .subscribe( | 112 | .subscribe( |
123 | () => { | 113 | () => { |
124 | this.notificationsService.success( | 114 | this.notifier.success(this.i18n('Account {{nameWithHost}} muted.', { nameWithHost: account.nameWithHost })) |
125 | this.i18n('Success'), | ||
126 | this.i18n('Account {{nameWithHost}} muted.', { nameWithHost: account.nameWithHost }) | ||
127 | ) | ||
128 | 115 | ||
129 | this.account.mutedByUser = true | 116 | this.account.mutedByUser = true |
130 | this.userChanged.emit() | 117 | this.userChanged.emit() |
131 | }, | 118 | }, |
132 | 119 | ||
133 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 120 | err => this.notifier.error(err.message) |
134 | ) | 121 | ) |
135 | } | 122 | } |
136 | 123 | ||
@@ -138,16 +125,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
138 | this.blocklistService.unblockAccountByUser(account) | 125 | this.blocklistService.unblockAccountByUser(account) |
139 | .subscribe( | 126 | .subscribe( |
140 | () => { | 127 | () => { |
141 | this.notificationsService.success( | 128 | this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: account.nameWithHost })) |
142 | this.i18n('Success'), | ||
143 | this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: account.nameWithHost }) | ||
144 | ) | ||
145 | 129 | ||
146 | this.account.mutedByUser = false | 130 | this.account.mutedByUser = false |
147 | this.userChanged.emit() | 131 | this.userChanged.emit() |
148 | }, | 132 | }, |
149 | 133 | ||
150 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 134 | err => this.notifier.error(err.message) |
151 | ) | 135 | ) |
152 | } | 136 | } |
153 | 137 | ||
@@ -155,16 +139,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
155 | this.blocklistService.blockServerByUser(host) | 139 | this.blocklistService.blockServerByUser(host) |
156 | .subscribe( | 140 | .subscribe( |
157 | () => { | 141 | () => { |
158 | this.notificationsService.success( | 142 | this.notifier.success(this.i18n('Instance {{host}} muted.', { host })) |
159 | this.i18n('Success'), | ||
160 | this.i18n('Instance {{host}} muted.', { host }) | ||
161 | ) | ||
162 | 143 | ||
163 | this.account.mutedServerByUser = true | 144 | this.account.mutedServerByUser = true |
164 | this.userChanged.emit() | 145 | this.userChanged.emit() |
165 | }, | 146 | }, |
166 | 147 | ||
167 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 148 | err => this.notifier.error(err.message) |
168 | ) | 149 | ) |
169 | } | 150 | } |
170 | 151 | ||
@@ -172,16 +153,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
172 | this.blocklistService.unblockServerByUser(host) | 153 | this.blocklistService.unblockServerByUser(host) |
173 | .subscribe( | 154 | .subscribe( |
174 | () => { | 155 | () => { |
175 | this.notificationsService.success( | 156 | this.notifier.success(this.i18n('Instance {{host}} unmuted.', { host })) |
176 | this.i18n('Success'), | ||
177 | this.i18n('Instance {{host}} unmuted.', { host }) | ||
178 | ) | ||
179 | 157 | ||
180 | this.account.mutedServerByUser = false | 158 | this.account.mutedServerByUser = false |
181 | this.userChanged.emit() | 159 | this.userChanged.emit() |
182 | }, | 160 | }, |
183 | 161 | ||
184 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 162 | err => this.notifier.error(err.message) |
185 | ) | 163 | ) |
186 | } | 164 | } |
187 | 165 | ||
@@ -189,16 +167,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
189 | this.blocklistService.blockAccountByInstance(account) | 167 | this.blocklistService.blockAccountByInstance(account) |
190 | .subscribe( | 168 | .subscribe( |
191 | () => { | 169 | () => { |
192 | this.notificationsService.success( | 170 | this.notifier.success(this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost })) |
193 | this.i18n('Success'), | ||
194 | this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost }) | ||
195 | ) | ||
196 | 171 | ||
197 | this.account.mutedByInstance = true | 172 | this.account.mutedByInstance = true |
198 | this.userChanged.emit() | 173 | this.userChanged.emit() |
199 | }, | 174 | }, |
200 | 175 | ||
201 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 176 | err => this.notifier.error(err.message) |
202 | ) | 177 | ) |
203 | } | 178 | } |
204 | 179 | ||
@@ -206,16 +181,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
206 | this.blocklistService.unblockAccountByInstance(account) | 181 | this.blocklistService.unblockAccountByInstance(account) |
207 | .subscribe( | 182 | .subscribe( |
208 | () => { | 183 | () => { |
209 | this.notificationsService.success( | 184 | this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted by the instance.', { nameWithHost: account.nameWithHost })) |
210 | this.i18n('Success'), | ||
211 | this.i18n('Account {{nameWithHost}} unmuted by the instance.', { nameWithHost: account.nameWithHost }) | ||
212 | ) | ||
213 | 185 | ||
214 | this.account.mutedByInstance = false | 186 | this.account.mutedByInstance = false |
215 | this.userChanged.emit() | 187 | this.userChanged.emit() |
216 | }, | 188 | }, |
217 | 189 | ||
218 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 190 | err => this.notifier.error(err.message) |
219 | ) | 191 | ) |
220 | } | 192 | } |
221 | 193 | ||
@@ -223,16 +195,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
223 | this.blocklistService.blockServerByInstance(host) | 195 | this.blocklistService.blockServerByInstance(host) |
224 | .subscribe( | 196 | .subscribe( |
225 | () => { | 197 | () => { |
226 | this.notificationsService.success( | 198 | this.notifier.success(this.i18n('Instance {{host}} muted by the instance.', { host })) |
227 | this.i18n('Success'), | ||
228 | this.i18n('Instance {{host}} muted by the instance.', { host }) | ||
229 | ) | ||
230 | 199 | ||
231 | this.account.mutedServerByInstance = true | 200 | this.account.mutedServerByInstance = true |
232 | this.userChanged.emit() | 201 | this.userChanged.emit() |
233 | }, | 202 | }, |
234 | 203 | ||
235 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 204 | err => this.notifier.error(err.message) |
236 | ) | 205 | ) |
237 | } | 206 | } |
238 | 207 | ||
@@ -240,16 +209,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
240 | this.blocklistService.unblockServerByInstance(host) | 209 | this.blocklistService.unblockServerByInstance(host) |
241 | .subscribe( | 210 | .subscribe( |
242 | () => { | 211 | () => { |
243 | this.notificationsService.success( | 212 | this.notifier.success(this.i18n('Instance {{host}} unmuted by the instance.', { host })) |
244 | this.i18n('Success'), | ||
245 | this.i18n('Instance {{host}} unmuted by the instance.', { host }) | ||
246 | ) | ||
247 | 213 | ||
248 | this.account.mutedServerByInstance = false | 214 | this.account.mutedServerByInstance = false |
249 | this.userChanged.emit() | 215 | this.userChanged.emit() |
250 | }, | 216 | }, |
251 | 217 | ||
252 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 218 | err => this.notifier.error(err.message) |
253 | ) | 219 | ) |
254 | } | 220 | } |
255 | 221 | ||
@@ -277,18 +243,18 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
277 | }, | 243 | }, |
278 | { | 244 | { |
279 | label: this.i18n('Ban'), | 245 | label: this.i18n('Ban'), |
280 | handler: ({ user }: { user: User }) => this.openBanUserModal(user), | 246 | handler: ({ user }) => this.openBanUserModal(user), |
281 | isDisplayed: ({ user }: { user: User }) => !user.blocked | 247 | isDisplayed: ({ user }) => !user.blocked |
282 | }, | 248 | }, |
283 | { | 249 | { |
284 | label: this.i18n('Unban'), | 250 | label: this.i18n('Unban'), |
285 | handler: ({ user }: { user: User }) => this.unbanUser(user), | 251 | handler: ({ user }) => this.unbanUser(user), |
286 | isDisplayed: ({ user }: { user: User }) => user.blocked | 252 | isDisplayed: ({ user }) => user.blocked |
287 | }, | 253 | }, |
288 | { | 254 | { |
289 | label: this.i18n('Set Email as Verified'), | 255 | label: this.i18n('Set Email as Verified'), |
290 | handler: ({ user }: { user: User }) => this.setEmailAsVerified(user), | 256 | handler: ({ user }) => this.setEmailAsVerified(user), |
291 | isDisplayed: ({ user }: { user: User }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false | 257 | isDisplayed: ({ user }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false |
292 | } | 258 | } |
293 | ]) | 259 | ]) |
294 | } | 260 | } |
@@ -299,23 +265,23 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
299 | this.userActions.push([ | 265 | this.userActions.push([ |
300 | { | 266 | { |
301 | label: this.i18n('Mute this account'), | 267 | label: this.i18n('Mute this account'), |
302 | isDisplayed: ({ account }: { account: Account }) => account.mutedByUser === false, | 268 | isDisplayed: ({ account }) => account.mutedByUser === false, |
303 | handler: ({ account }: { account: Account }) => this.blockAccountByUser(account) | 269 | handler: ({ account }) => this.blockAccountByUser(account) |
304 | }, | 270 | }, |
305 | { | 271 | { |
306 | label: this.i18n('Unmute this account'), | 272 | label: this.i18n('Unmute this account'), |
307 | isDisplayed: ({ account }: { account: Account }) => account.mutedByUser === true, | 273 | isDisplayed: ({ account }) => account.mutedByUser === true, |
308 | handler: ({ account }: { account: Account }) => this.unblockAccountByUser(account) | 274 | handler: ({ account }) => this.unblockAccountByUser(account) |
309 | }, | 275 | }, |
310 | { | 276 | { |
311 | label: this.i18n('Mute the instance'), | 277 | label: this.i18n('Mute the instance'), |
312 | isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === false, | 278 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false, |
313 | handler: ({ account }: { account: Account }) => this.blockServerByUser(account.host) | 279 | handler: ({ account }) => this.blockServerByUser(account.host) |
314 | }, | 280 | }, |
315 | { | 281 | { |
316 | label: this.i18n('Unmute the instance'), | 282 | label: this.i18n('Unmute the instance'), |
317 | isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === true, | 283 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, |
318 | handler: ({ account }: { account: Account }) => this.unblockServerByUser(account.host) | 284 | handler: ({ account }) => this.unblockServerByUser(account.host) |
319 | } | 285 | } |
320 | ]) | 286 | ]) |
321 | 287 | ||
@@ -326,13 +292,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
326 | instanceActions = instanceActions.concat([ | 292 | instanceActions = instanceActions.concat([ |
327 | { | 293 | { |
328 | label: this.i18n('Mute this account by your instance'), | 294 | label: this.i18n('Mute this account by your instance'), |
329 | isDisplayed: ({ account }: { account: Account }) => account.mutedByInstance === false, | 295 | isDisplayed: ({ account }) => account.mutedByInstance === false, |
330 | handler: ({ account }: { account: Account }) => this.blockAccountByInstance(account) | 296 | handler: ({ account }) => this.blockAccountByInstance(account) |
331 | }, | 297 | }, |
332 | { | 298 | { |
333 | label: this.i18n('Unmute this account by your instance'), | 299 | label: this.i18n('Unmute this account by your instance'), |
334 | isDisplayed: ({ account }: { account: Account }) => account.mutedByInstance === true, | 300 | isDisplayed: ({ account }) => account.mutedByInstance === true, |
335 | handler: ({ account }: { account: Account }) => this.unblockAccountByInstance(account) | 301 | handler: ({ account }) => this.unblockAccountByInstance(account) |
336 | } | 302 | } |
337 | ]) | 303 | ]) |
338 | } | 304 | } |
@@ -342,13 +308,13 @@ export class UserModerationDropdownComponent implements OnChanges { | |||
342 | instanceActions = instanceActions.concat([ | 308 | instanceActions = instanceActions.concat([ |
343 | { | 309 | { |
344 | label: this.i18n('Mute the instance by your instance'), | 310 | label: this.i18n('Mute the instance by your instance'), |
345 | isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === false, | 311 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false, |
346 | handler: ({ account }: { account: Account }) => this.blockServerByInstance(account.host) | 312 | handler: ({ account }) => this.blockServerByInstance(account.host) |
347 | }, | 313 | }, |
348 | { | 314 | { |
349 | label: this.i18n('Unmute the instance by your instance'), | 315 | label: this.i18n('Unmute the instance by your instance'), |
350 | isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === true, | 316 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, |
351 | handler: ({ account }: { account: Account }) => this.unblockServerByInstance(account.host) | 317 | handler: ({ account }) => this.unblockServerByInstance(account.host) |
352 | } | 318 | } |
353 | ]) | 319 | ]) |
354 | } | 320 | } |
diff --git a/client/src/app/shared/renderer/html-renderer.service.ts b/client/src/app/shared/renderer/html-renderer.service.ts new file mode 100644 index 000000000..d49df9b6d --- /dev/null +++ b/client/src/app/shared/renderer/html-renderer.service.ts | |||
@@ -0,0 +1,35 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { LinkifierService } from '@app/shared/renderer/linkifier.service' | ||
3 | import * as sanitizeHtml from 'sanitize-html' | ||
4 | |||
5 | @Injectable() | ||
6 | export class HtmlRendererService { | ||
7 | |||
8 | constructor (private linkifier: LinkifierService) { | ||
9 | |||
10 | } | ||
11 | |||
12 | toSafeHtml (text: string) { | ||
13 | // Convert possible markdown to html | ||
14 | const html = this.linkifier.linkify(text) | ||
15 | |||
16 | return sanitizeHtml(html, { | ||
17 | allowedTags: [ 'a', 'p', 'span', 'br' ], | ||
18 | allowedSchemes: [ 'http', 'https' ], | ||
19 | allowedAttributes: { | ||
20 | 'a': [ 'href', 'class', 'target' ] | ||
21 | }, | ||
22 | transformTags: { | ||
23 | a: (tagName, attribs) => { | ||
24 | return { | ||
25 | tagName, | ||
26 | attribs: Object.assign(attribs, { | ||
27 | target: '_blank', | ||
28 | rel: 'noopener noreferrer' | ||
29 | }) | ||
30 | } | ||
31 | } | ||
32 | } | ||
33 | }) | ||
34 | } | ||
35 | } | ||
diff --git a/client/src/app/shared/renderer/index.ts b/client/src/app/shared/renderer/index.ts new file mode 100644 index 000000000..39202b385 --- /dev/null +++ b/client/src/app/shared/renderer/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './html-renderer.service' | ||
2 | export * from './linkifier.service' | ||
3 | export * from './markdown.service' | ||
diff --git a/client/src/app/videos/+video-watch/comment/linkifier.service.ts b/client/src/app/shared/renderer/linkifier.service.ts index 2529c9eaf..2529c9eaf 100644 --- a/client/src/app/videos/+video-watch/comment/linkifier.service.ts +++ b/client/src/app/shared/renderer/linkifier.service.ts | |||
diff --git a/client/src/app/videos/shared/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts index 07017eca5..07017eca5 100644 --- a/client/src/app/videos/shared/markdown.service.ts +++ b/client/src/app/shared/renderer/markdown.service.ts | |||
diff --git a/client/src/app/shared/rest/component-pagination.model.ts b/client/src/app/shared/rest/component-pagination.model.ts index 0b8ecc318..85160d445 100644 --- a/client/src/app/shared/rest/component-pagination.model.ts +++ b/client/src/app/shared/rest/component-pagination.model.ts | |||
@@ -3,3 +3,14 @@ export interface ComponentPagination { | |||
3 | itemsPerPage: number | 3 | itemsPerPage: number |
4 | totalItems?: number | 4 | totalItems?: number |
5 | } | 5 | } |
6 | |||
7 | export function hasMoreItems (componentPagination: ComponentPagination) { | ||
8 | // No results | ||
9 | if (componentPagination.totalItems === 0) return false | ||
10 | |||
11 | // Not loaded yet | ||
12 | if (!componentPagination.totalItems) return true | ||
13 | |||
14 | const maxPage = componentPagination.totalItems / componentPagination.itemsPerPage | ||
15 | return maxPage > componentPagination.currentPage | ||
16 | } | ||
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts index f149569ef..e6518dd1d 100644 --- a/client/src/app/shared/rest/rest-extractor.service.ts +++ b/client/src/app/shared/rest/rest-extractor.service.ts | |||
@@ -80,6 +80,7 @@ export class RestExtractor { | |||
80 | errorMessage = errorMessage ? errorMessage : 'Unknown error.' | 80 | errorMessage = errorMessage ? errorMessage : 'Unknown error.' |
81 | console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) | 81 | console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) |
82 | } else { | 82 | } else { |
83 | console.error(err) | ||
83 | errorMessage = err | 84 | errorMessage = err |
84 | } | 85 | } |
85 | 86 | ||
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index a2fa27b72..6f8625c7e 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -6,7 +6,6 @@ import { RouterModule } from '@angular/router' | |||
6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | 6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' |
7 | import { HelpComponent } from '@app/shared/misc/help.component' | 7 | import { HelpComponent } from '@app/shared/misc/help.component' |
8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | 8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' |
9 | import { MarkdownService } from '@app/videos/shared' | ||
10 | 9 | ||
11 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | 10 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' |
12 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' | 11 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' |
@@ -34,6 +33,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
34 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 33 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
35 | import { | 34 | import { |
36 | CustomConfigValidatorsService, | 35 | CustomConfigValidatorsService, |
36 | InstanceValidatorsService, | ||
37 | LoginValidatorsService, | 37 | LoginValidatorsService, |
38 | ReactiveFileComponent, | 38 | ReactiveFileComponent, |
39 | ResetPasswordValidatorsService, | 39 | ResetPasswordValidatorsService, |
@@ -61,6 +61,14 @@ import { OverviewService } from '@app/shared/overview' | |||
61 | import { UserBanModalComponent } from '@app/shared/moderation' | 61 | import { UserBanModalComponent } from '@app/shared/moderation' |
62 | import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' | 62 | import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' |
63 | import { BlocklistService } from '@app/shared/blocklist' | 63 | import { BlocklistService } from '@app/shared/blocklist' |
64 | import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' | ||
65 | import { UserHistoryService } from '@app/shared/users/user-history.service' | ||
66 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | ||
67 | import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' | ||
68 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
69 | import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' | ||
70 | import { ConfirmComponent } from '@app/shared/confirm/confirm.component' | ||
71 | import { GlobalIconComponent } from '@app/shared/icons/global-icon.component' | ||
64 | 72 | ||
65 | @NgModule({ | 73 | @NgModule({ |
66 | imports: [ | 74 | imports: [ |
@@ -102,7 +110,11 @@ import { BlocklistService } from '@app/shared/blocklist' | |||
102 | RemoteSubscribeComponent, | 110 | RemoteSubscribeComponent, |
103 | InstanceFeaturesTableComponent, | 111 | InstanceFeaturesTableComponent, |
104 | UserBanModalComponent, | 112 | UserBanModalComponent, |
105 | UserModerationDropdownComponent | 113 | UserModerationDropdownComponent, |
114 | TopMenuDropdownComponent, | ||
115 | UserNotificationsComponent, | ||
116 | ConfirmComponent, | ||
117 | GlobalIconComponent | ||
106 | ], | 118 | ], |
107 | 119 | ||
108 | exports: [ | 120 | exports: [ |
@@ -141,6 +153,10 @@ import { BlocklistService } from '@app/shared/blocklist' | |||
141 | InstanceFeaturesTableComponent, | 153 | InstanceFeaturesTableComponent, |
142 | UserBanModalComponent, | 154 | UserBanModalComponent, |
143 | UserModerationDropdownComponent, | 155 | UserModerationDropdownComponent, |
156 | TopMenuDropdownComponent, | ||
157 | UserNotificationsComponent, | ||
158 | ConfirmComponent, | ||
159 | GlobalIconComponent, | ||
144 | 160 | ||
145 | NumberFormatterPipe, | 161 | NumberFormatterPipe, |
146 | ObjectLengthPipe, | 162 | ObjectLengthPipe, |
@@ -157,7 +173,6 @@ import { BlocklistService } from '@app/shared/blocklist' | |||
157 | UserService, | 173 | UserService, |
158 | VideoService, | 174 | VideoService, |
159 | AccountService, | 175 | AccountService, |
160 | MarkdownService, | ||
161 | VideoChannelService, | 176 | VideoChannelService, |
162 | VideoCaptionService, | 177 | VideoCaptionService, |
163 | VideoImportService, | 178 | VideoImportService, |
@@ -177,11 +192,20 @@ import { BlocklistService } from '@app/shared/blocklist' | |||
177 | OverviewService, | 192 | OverviewService, |
178 | VideoChangeOwnershipValidatorsService, | 193 | VideoChangeOwnershipValidatorsService, |
179 | VideoAcceptOwnershipValidatorsService, | 194 | VideoAcceptOwnershipValidatorsService, |
195 | InstanceValidatorsService, | ||
180 | BlocklistService, | 196 | BlocklistService, |
197 | UserHistoryService, | ||
198 | InstanceService, | ||
199 | |||
200 | MarkdownService, | ||
201 | LinkifierService, | ||
202 | HtmlRendererService, | ||
181 | 203 | ||
182 | I18nPrimengCalendarService, | 204 | I18nPrimengCalendarService, |
183 | ScreenService, | 205 | ScreenService, |
184 | 206 | ||
207 | UserNotificationService, | ||
208 | |||
185 | I18n | 209 | I18n |
186 | ] | 210 | ] |
187 | }) | 211 | }) |
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.ts b/client/src/app/shared/user-subscription/remote-subscribe.component.ts index 7a81108cd..ba2a45df1 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.ts +++ b/client/src/app/shared/user-subscription/remote-subscribe.component.ts | |||
@@ -29,7 +29,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit { | |||
29 | } | 29 | } |
30 | 30 | ||
31 | onValidKey () { | 31 | onValidKey () { |
32 | this.onValueChanged() | 32 | this.check() |
33 | if (!this.form.valid) return | 33 | if (!this.form.valid) return |
34 | 34 | ||
35 | this.formValidated() | 35 | this.formValidated() |
@@ -37,7 +37,24 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit { | |||
37 | 37 | ||
38 | formValidated () { | 38 | formValidated () { |
39 | const address = this.form.value['text'] | 39 | const address = this.form.value['text'] |
40 | const [ , hostname ] = address.split('@') | 40 | const [ username, hostname ] = address.split('@') |
41 | window.open(`https://${hostname}/authorize_interaction?acct=${this.account}`) | 41 | |
42 | fetch(`https://${hostname}/.well-known/webfinger?resource=acct:${username}@${hostname}`) | ||
43 | .then(response => response.json()) | ||
44 | .then(data => new Promise((resolve, reject) => { | ||
45 | if (data && Array.isArray(data.links)) { | ||
46 | const link: { | ||
47 | template: string | ||
48 | } = data.links.find((link: any) => | ||
49 | link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe') | ||
50 | |||
51 | if (link && link.template.includes('{uri}')) { | ||
52 | resolve(link.template.replace('{uri}', `acct:${this.account}`)) | ||
53 | } | ||
54 | } | ||
55 | reject() | ||
56 | })) | ||
57 | .then(window.open) | ||
58 | .catch(() => window.open(`https://${hostname}/authorize_interaction?acct=${this.account}`)) | ||
42 | } | 59 | } |
43 | } | 60 | } |
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.ts b/client/src/app/shared/user-subscription/subscribe-button.component.ts index 315ea5037..8f1754c7f 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.ts +++ b/client/src/app/shared/user-subscription/subscribe-button.component.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { AuthService } from '@app/core' | 3 | import { AuthService, Notifier } from '@app/core' |
4 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | 4 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
6 | import { NotificationsService } from 'angular2-notifications' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { VideoService } from '@app/shared/video/video.service' | 7 | import { VideoService } from '@app/shared/video/video.service' |
9 | import { FeedFormat } from '../../../../../shared/models/feeds' | 8 | import { FeedFormat } from '../../../../../shared/models/feeds' |
@@ -23,7 +22,7 @@ export class SubscribeButtonComponent implements OnInit { | |||
23 | constructor ( | 22 | constructor ( |
24 | private authService: AuthService, | 23 | private authService: AuthService, |
25 | private router: Router, | 24 | private router: Router, |
26 | private notificationsService: NotificationsService, | 25 | private notifier: Notifier, |
27 | private userSubscriptionService: UserSubscriptionService, | 26 | private userSubscriptionService: UserSubscriptionService, |
28 | private i18n: I18n, | 27 | private i18n: I18n, |
29 | private videoService: VideoService | 28 | private videoService: VideoService |
@@ -43,18 +42,17 @@ export class SubscribeButtonComponent implements OnInit { | |||
43 | .subscribe( | 42 | .subscribe( |
44 | res => this.subscribed = res[this.uri], | 43 | res => this.subscribed = res[this.uri], |
45 | 44 | ||
46 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 45 | err => this.notifier.error(err.message) |
47 | ) | 46 | ) |
48 | } | 47 | } |
49 | } | 48 | } |
50 | 49 | ||
51 | subscribe () { | 50 | subscribe () { |
52 | if (this.isUserLoggedIn()) { | 51 | if (this.isUserLoggedIn()) { |
53 | this.localSubscribe() | 52 | return this.localSubscribe() |
54 | } else { | ||
55 | this.authService.redirectUrl = this.router.url | ||
56 | this.gotoLogin() | ||
57 | } | 53 | } |
54 | |||
55 | return this.gotoLogin() | ||
58 | } | 56 | } |
59 | 57 | ||
60 | localSubscribe () { | 58 | localSubscribe () { |
@@ -63,13 +61,13 @@ export class SubscribeButtonComponent implements OnInit { | |||
63 | () => { | 61 | () => { |
64 | this.subscribed = true | 62 | this.subscribed = true |
65 | 63 | ||
66 | this.notificationsService.success( | 64 | this.notifier.success( |
67 | this.i18n('Subscribed'), | 65 | this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }), |
68 | this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }) | 66 | this.i18n('Subscribed') |
69 | ) | 67 | ) |
70 | }, | 68 | }, |
71 | 69 | ||
72 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 70 | err => this.notifier.error(err.message) |
73 | ) | 71 | ) |
74 | } | 72 | } |
75 | 73 | ||
@@ -85,13 +83,13 @@ export class SubscribeButtonComponent implements OnInit { | |||
85 | () => { | 83 | () => { |
86 | this.subscribed = false | 84 | this.subscribed = false |
87 | 85 | ||
88 | this.notificationsService.success( | 86 | this.notifier.success( |
89 | this.i18n('Unsubscribed'), | 87 | this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }), |
90 | this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }) | 88 | this.i18n('Unsubscribed') |
91 | ) | 89 | ) |
92 | }, | 90 | }, |
93 | 91 | ||
94 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 92 | err => this.notifier.error(err.message) |
95 | ) | 93 | ) |
96 | } | 94 | } |
97 | 95 | ||
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts index 7b5a67bc7..ebd715fb1 100644 --- a/client/src/app/shared/users/index.ts +++ b/client/src/app/shared/users/index.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | export * from './user.model' | 1 | export * from './user.model' |
2 | export * from './user.service' | 2 | export * from './user.service' |
3 | export * from './user-notifications.component' | ||
diff --git a/client/src/app/shared/users/user-history.service.ts b/client/src/app/shared/users/user-history.service.ts new file mode 100644 index 000000000..9ed25bfc7 --- /dev/null +++ b/client/src/app/shared/users/user-history.service.ts | |||
@@ -0,0 +1,45 @@ | |||
1 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { environment } from '../../../environments/environment' | ||
4 | import { RestExtractor } from '../rest/rest-extractor.service' | ||
5 | import { RestService } from '../rest/rest.service' | ||
6 | import { Video } from '../video/video.model' | ||
7 | import { catchError, map, switchMap } from 'rxjs/operators' | ||
8 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
9 | import { VideoService } from '@app/shared/video/video.service' | ||
10 | import { ResultList } from '../../../../../shared' | ||
11 | |||
12 | @Injectable() | ||
13 | export class UserHistoryService { | ||
14 | static BASE_USER_VIDEOS_HISTORY_URL = environment.apiUrl + '/api/v1/users/me/history/videos' | ||
15 | |||
16 | constructor ( | ||
17 | private authHttp: HttpClient, | ||
18 | private restExtractor: RestExtractor, | ||
19 | private restService: RestService, | ||
20 | private videoService: VideoService | ||
21 | ) {} | ||
22 | |||
23 | getUserVideosHistory (historyPagination: ComponentPagination) { | ||
24 | const pagination = this.restService.componentPaginationToRestPagination(historyPagination) | ||
25 | |||
26 | let params = new HttpParams() | ||
27 | params = this.restService.addRestGetParams(params, pagination) | ||
28 | |||
29 | return this.authHttp | ||
30 | .get<ResultList<Video>>(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL, { params }) | ||
31 | .pipe( | ||
32 | switchMap(res => this.videoService.extractVideos(res)), | ||
33 | catchError(err => this.restExtractor.handleError(err)) | ||
34 | ) | ||
35 | } | ||
36 | |||
37 | deleteUserVideosHistory () { | ||
38 | return this.authHttp | ||
39 | .post(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL + '/remove', {}) | ||
40 | .pipe( | ||
41 | map(() => this.restExtractor.extractDataBool()), | ||
42 | catchError(err => this.restExtractor.handleError(err)) | ||
43 | ) | ||
44 | } | ||
45 | } | ||
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts new file mode 100644 index 000000000..125d2120c --- /dev/null +++ b/client/src/app/shared/users/user-notification.model.ts | |||
@@ -0,0 +1,155 @@ | |||
1 | import { UserNotification as UserNotificationServer, UserNotificationType, VideoInfo, ActorInfo } from '../../../../../shared' | ||
2 | import { Actor } from '@app/shared/actor/actor.model' | ||
3 | |||
4 | export class UserNotification implements UserNotificationServer { | ||
5 | id: number | ||
6 | type: UserNotificationType | ||
7 | read: boolean | ||
8 | |||
9 | video?: VideoInfo & { | ||
10 | channel: ActorInfo & { avatarUrl?: string } | ||
11 | } | ||
12 | |||
13 | videoImport?: { | ||
14 | id: number | ||
15 | video?: VideoInfo | ||
16 | torrentName?: string | ||
17 | magnetUri?: string | ||
18 | targetUrl?: string | ||
19 | } | ||
20 | |||
21 | comment?: { | ||
22 | id: number | ||
23 | threadId: number | ||
24 | account: ActorInfo & { avatarUrl?: string } | ||
25 | video: VideoInfo | ||
26 | } | ||
27 | |||
28 | videoAbuse?: { | ||
29 | id: number | ||
30 | video: VideoInfo | ||
31 | } | ||
32 | |||
33 | videoBlacklist?: { | ||
34 | id: number | ||
35 | video: VideoInfo | ||
36 | } | ||
37 | |||
38 | account?: ActorInfo & { avatarUrl?: string } | ||
39 | |||
40 | actorFollow?: { | ||
41 | id: number | ||
42 | follower: ActorInfo & { avatarUrl?: string } | ||
43 | following: { | ||
44 | type: 'account' | 'channel' | ||
45 | name: string | ||
46 | displayName: string | ||
47 | } | ||
48 | } | ||
49 | |||
50 | createdAt: string | ||
51 | updatedAt: string | ||
52 | |||
53 | // Additional fields | ||
54 | videoUrl?: string | ||
55 | commentUrl?: any[] | ||
56 | videoAbuseUrl?: string | ||
57 | accountUrl?: string | ||
58 | videoImportIdentifier?: string | ||
59 | videoImportUrl?: string | ||
60 | |||
61 | constructor (hash: UserNotificationServer) { | ||
62 | this.id = hash.id | ||
63 | this.type = hash.type | ||
64 | this.read = hash.read | ||
65 | |||
66 | this.video = hash.video | ||
67 | if (this.video) this.setAvatarUrl(this.video.channel) | ||
68 | |||
69 | this.videoImport = hash.videoImport | ||
70 | |||
71 | this.comment = hash.comment | ||
72 | if (this.comment) this.setAvatarUrl(this.comment.account) | ||
73 | |||
74 | this.videoAbuse = hash.videoAbuse | ||
75 | |||
76 | this.videoBlacklist = hash.videoBlacklist | ||
77 | |||
78 | this.account = hash.account | ||
79 | if (this.account) this.setAvatarUrl(this.account) | ||
80 | |||
81 | this.actorFollow = hash.actorFollow | ||
82 | if (this.actorFollow) this.setAvatarUrl(this.actorFollow.follower) | ||
83 | |||
84 | this.createdAt = hash.createdAt | ||
85 | this.updatedAt = hash.updatedAt | ||
86 | |||
87 | switch (this.type) { | ||
88 | case UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION: | ||
89 | this.videoUrl = this.buildVideoUrl(this.video) | ||
90 | break | ||
91 | |||
92 | case UserNotificationType.UNBLACKLIST_ON_MY_VIDEO: | ||
93 | this.videoUrl = this.buildVideoUrl(this.video) | ||
94 | break | ||
95 | |||
96 | case UserNotificationType.NEW_COMMENT_ON_MY_VIDEO: | ||
97 | case UserNotificationType.COMMENT_MENTION: | ||
98 | this.accountUrl = this.buildAccountUrl(this.comment.account) | ||
99 | this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ] | ||
100 | break | ||
101 | |||
102 | case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS: | ||
103 | this.videoAbuseUrl = '/admin/moderation/video-abuses/list' | ||
104 | this.videoUrl = this.buildVideoUrl(this.videoAbuse.video) | ||
105 | break | ||
106 | |||
107 | case UserNotificationType.BLACKLIST_ON_MY_VIDEO: | ||
108 | this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) | ||
109 | break | ||
110 | |||
111 | case UserNotificationType.MY_VIDEO_PUBLISHED: | ||
112 | this.videoUrl = this.buildVideoUrl(this.video) | ||
113 | break | ||
114 | |||
115 | case UserNotificationType.MY_VIDEO_IMPORT_SUCCESS: | ||
116 | this.videoImportUrl = this.buildVideoImportUrl() | ||
117 | this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport) | ||
118 | this.videoUrl = this.buildVideoUrl(this.videoImport.video) | ||
119 | break | ||
120 | |||
121 | case UserNotificationType.MY_VIDEO_IMPORT_ERROR: | ||
122 | this.videoImportUrl = this.buildVideoImportUrl() | ||
123 | this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport) | ||
124 | break | ||
125 | |||
126 | case UserNotificationType.NEW_USER_REGISTRATION: | ||
127 | this.accountUrl = this.buildAccountUrl(this.account) | ||
128 | break | ||
129 | |||
130 | case UserNotificationType.NEW_FOLLOW: | ||
131 | this.accountUrl = this.buildAccountUrl(this.actorFollow.follower) | ||
132 | break | ||
133 | } | ||
134 | } | ||
135 | |||
136 | private buildVideoUrl (video: { uuid: string }) { | ||
137 | return '/videos/watch/' + video.uuid | ||
138 | } | ||
139 | |||
140 | private buildAccountUrl (account: { name: string, host: string }) { | ||
141 | return '/accounts/' + Actor.CREATE_BY_STRING(account.name, account.host) | ||
142 | } | ||
143 | |||
144 | private buildVideoImportUrl () { | ||
145 | return '/my-account/video-imports' | ||
146 | } | ||
147 | |||
148 | private buildVideoImportIdentifier (videoImport: { targetUrl?: string, magnetUri?: string, torrentName?: string }) { | ||
149 | return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName | ||
150 | } | ||
151 | |||
152 | private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { path: string } }) { | ||
153 | actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) | ||
154 | } | ||
155 | } | ||
diff --git a/client/src/app/shared/users/user-notification.service.ts b/client/src/app/shared/users/user-notification.service.ts new file mode 100644 index 000000000..f8a30955d --- /dev/null +++ b/client/src/app/shared/users/user-notification.service.ts | |||
@@ -0,0 +1,86 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
3 | import { RestExtractor, RestService } from '../rest' | ||
4 | import { catchError, map, tap } from 'rxjs/operators' | ||
5 | import { environment } from '../../../environments/environment' | ||
6 | import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '../../../../../shared' | ||
7 | import { UserNotification } from './user-notification.model' | ||
8 | import { AuthService } from '../../core' | ||
9 | import { ComponentPagination } from '../rest/component-pagination.model' | ||
10 | import { User } from '..' | ||
11 | import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service' | ||
12 | |||
13 | @Injectable() | ||
14 | export class UserNotificationService { | ||
15 | static BASE_NOTIFICATIONS_URL = environment.apiUrl + '/api/v1/users/me/notifications' | ||
16 | static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings' | ||
17 | |||
18 | constructor ( | ||
19 | private auth: AuthService, | ||
20 | private authHttp: HttpClient, | ||
21 | private restExtractor: RestExtractor, | ||
22 | private restService: RestService, | ||
23 | private userNotificationSocket: UserNotificationSocket | ||
24 | ) {} | ||
25 | |||
26 | listMyNotifications (pagination: ComponentPagination, unread?: boolean, ignoreLoadingBar = false) { | ||
27 | let params = new HttpParams() | ||
28 | params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination)) | ||
29 | |||
30 | if (unread) params = params.append('unread', `${unread}`) | ||
31 | |||
32 | const headers = ignoreLoadingBar ? { ignoreLoadingBar: '' } : undefined | ||
33 | |||
34 | return this.authHttp.get<ResultList<UserNotification>>(UserNotificationService.BASE_NOTIFICATIONS_URL, { params, headers }) | ||
35 | .pipe( | ||
36 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | ||
37 | map(res => this.restExtractor.applyToResultListData(res, this.formatNotification.bind(this))), | ||
38 | catchError(err => this.restExtractor.handleError(err)) | ||
39 | ) | ||
40 | } | ||
41 | |||
42 | countUnreadNotifications () { | ||
43 | return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true) | ||
44 | .pipe(map(n => n.total)) | ||
45 | } | ||
46 | |||
47 | markAsRead (notification: UserNotification) { | ||
48 | const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read' | ||
49 | |||
50 | const body = { ids: [ notification.id ] } | ||
51 | const headers = { ignoreLoadingBar: '' } | ||
52 | |||
53 | return this.authHttp.post(url, body, { headers }) | ||
54 | .pipe( | ||
55 | map(this.restExtractor.extractDataBool), | ||
56 | tap(() => this.userNotificationSocket.dispatch('read')), | ||
57 | catchError(res => this.restExtractor.handleError(res)) | ||
58 | ) | ||
59 | } | ||
60 | |||
61 | markAllAsRead () { | ||
62 | const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read-all' | ||
63 | const headers = { ignoreLoadingBar: '' } | ||
64 | |||
65 | return this.authHttp.post(url, {}, { headers }) | ||
66 | .pipe( | ||
67 | map(this.restExtractor.extractDataBool), | ||
68 | tap(() => this.userNotificationSocket.dispatch('read-all')), | ||
69 | catchError(res => this.restExtractor.handleError(res)) | ||
70 | ) | ||
71 | } | ||
72 | |||
73 | updateNotificationSettings (user: User, settings: UserNotificationSetting) { | ||
74 | const url = UserNotificationService.BASE_NOTIFICATION_SETTINGS | ||
75 | |||
76 | return this.authHttp.put(url, settings) | ||
77 | .pipe( | ||
78 | map(this.restExtractor.extractDataBool), | ||
79 | catchError(res => this.restExtractor.handleError(res)) | ||
80 | ) | ||
81 | } | ||
82 | |||
83 | private formatNotification (notification: UserNotificationServer) { | ||
84 | return new UserNotification(notification) | ||
85 | } | ||
86 | } | ||
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html new file mode 100644 index 000000000..0d69e0feb --- /dev/null +++ b/client/src/app/shared/users/user-notifications.component.html | |||
@@ -0,0 +1,101 @@ | |||
1 | <div *ngIf="componentPagination.totalItems === 0" class="no-notification" i18n>You don't have notifications.</div> | ||
2 | |||
3 | <div class="notifications" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()"> | ||
4 | <div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)"> | ||
5 | |||
6 | <ng-container [ngSwitch]="notification.type"> | ||
7 | <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION"> | ||
8 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" /> | ||
9 | |||
10 | <div class="message"> | ||
11 | {{ notification.video.channel.displayName }} published a <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">new video</a> | ||
12 | </div> | ||
13 | </ng-container> | ||
14 | |||
15 | <ng-container i18n *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO"> | ||
16 | <my-global-icon iconName="undo"></my-global-icon> | ||
17 | |||
18 | <div class="message"> | ||
19 | Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been unblacklisted | ||
20 | </div> | ||
21 | </ng-container> | ||
22 | |||
23 | <ng-container i18n *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO"> | ||
24 | <my-global-icon iconName="no"></my-global-icon> | ||
25 | |||
26 | <div class="message"> | ||
27 | Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been blacklisted | ||
28 | </div> | ||
29 | </ng-container> | ||
30 | |||
31 | <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS"> | ||
32 | <my-global-icon iconName="alert"></my-global-icon> | ||
33 | |||
34 | <div class="message"> | ||
35 | <a (click)="markAsRead(notification)" [routerLink]="notification.videoAbuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoAbuse.video.name }}</a> | ||
36 | </div> | ||
37 | </ng-container> | ||
38 | |||
39 | <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO"> | ||
40 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> | ||
41 | |||
42 | <div class="message"> | ||
43 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a> | ||
44 | </div> | ||
45 | </ng-container> | ||
46 | |||
47 | <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_PUBLISHED"> | ||
48 | <my-global-icon iconName="sparkle"></my-global-icon> | ||
49 | |||
50 | <div class="message"> | ||
51 | Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been published | ||
52 | </div> | ||
53 | </ng-container> | ||
54 | |||
55 | <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_SUCCESS"> | ||
56 | <my-global-icon iconName="cloud-download"></my-global-icon> | ||
57 | |||
58 | <div class="message"> | ||
59 | <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">Your video import</a> {{ notification.videoImportIdentifier }} succeeded | ||
60 | </div> | ||
61 | </ng-container> | ||
62 | |||
63 | <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_ERROR"> | ||
64 | <my-global-icon iconName="cloud-error"></my-global-icon> | ||
65 | |||
66 | <div class="message"> | ||
67 | <a (click)="markAsRead(notification)" [routerLink]="notification.videoImportUrl">Your video import</a> {{ notification.videoImportIdentifier }} failed | ||
68 | </div> | ||
69 | </ng-container> | ||
70 | |||
71 | <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_USER_REGISTRATION"> | ||
72 | <my-global-icon iconName="user-add"></my-global-icon> | ||
73 | |||
74 | <div class="message"> | ||
75 | User <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.account.name }} registered</a> on your instance | ||
76 | </div> | ||
77 | </ng-container> | ||
78 | |||
79 | <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_FOLLOW"> | ||
80 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" /> | ||
81 | |||
82 | <div class="message"> | ||
83 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.actorFollow.follower.displayName }}</a> is following | ||
84 | |||
85 | <ng-container *ngIf="notification.actorFollow.following.type === 'channel'">your channel {{ notification.actorFollow.following.displayName }}</ng-container> | ||
86 | <ng-container *ngIf="notification.actorFollow.following.type === 'account'">your account</ng-container> | ||
87 | </div> | ||
88 | </ng-container> | ||
89 | |||
90 | <ng-container i18n *ngSwitchCase="UserNotificationType.COMMENT_MENTION"> | ||
91 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> | ||
92 | |||
93 | <div class="message"> | ||
94 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> mentioned you on <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">video {{ notification.comment.video.name }}</a> | ||
95 | </div> | ||
96 | </ng-container> | ||
97 | </ng-container> | ||
98 | |||
99 | <div class="from-date">{{ notification.createdAt | myFromNow }}</div> | ||
100 | </div> | ||
101 | </div> | ||
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/users/user-notifications.component.scss new file mode 100644 index 000000000..315d504c9 --- /dev/null +++ b/client/src/app/shared/users/user-notifications.component.scss | |||
@@ -0,0 +1,51 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .no-notification { | ||
5 | display: flex; | ||
6 | justify-content: center; | ||
7 | align-items: center; | ||
8 | padding: 20px 0; | ||
9 | } | ||
10 | |||
11 | .notification { | ||
12 | display: flex; | ||
13 | align-items: center; | ||
14 | font-size: inherit; | ||
15 | padding: 15px 5px 15px 10px; | ||
16 | border-bottom: 1px solid rgba(0, 0, 0, 0.10); | ||
17 | |||
18 | &.unread { | ||
19 | background-color: rgba(0, 0, 0, 0.05); | ||
20 | } | ||
21 | |||
22 | my-global-icon { | ||
23 | width: 24px; | ||
24 | margin-right: 11px; | ||
25 | margin-left: 3px; | ||
26 | |||
27 | @include apply-svg-color(#333); | ||
28 | } | ||
29 | |||
30 | .avatar { | ||
31 | @include avatar(30px); | ||
32 | |||
33 | margin-right: 10px; | ||
34 | } | ||
35 | |||
36 | .message { | ||
37 | flex-grow: 1; | ||
38 | |||
39 | a { | ||
40 | font-weight: $font-semibold; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | .from-date { | ||
45 | font-size: 0.85em; | ||
46 | color: $grey-foreground-color; | ||
47 | padding-left: 5px; | ||
48 | min-width: 70px; | ||
49 | text-align: right; | ||
50 | } | ||
51 | } | ||
diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/users/user-notifications.component.ts new file mode 100644 index 000000000..b5f9fd399 --- /dev/null +++ b/client/src/app/shared/users/user-notifications.component.ts | |||
@@ -0,0 +1,87 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | ||
3 | import { UserNotificationType } from '../../../../../shared' | ||
4 | import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' | ||
5 | import { Notifier } from '@app/core' | ||
6 | import { UserNotification } from '@app/shared/users/user-notification.model' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-user-notifications', | ||
10 | templateUrl: 'user-notifications.component.html', | ||
11 | styleUrls: [ 'user-notifications.component.scss' ] | ||
12 | }) | ||
13 | export class UserNotificationsComponent implements OnInit { | ||
14 | @Input() ignoreLoadingBar = false | ||
15 | @Input() infiniteScroll = true | ||
16 | @Input() itemsPerPage = 20 | ||
17 | |||
18 | notifications: UserNotification[] = [] | ||
19 | |||
20 | // So we can access it in the template | ||
21 | UserNotificationType = UserNotificationType | ||
22 | |||
23 | componentPagination: ComponentPagination | ||
24 | |||
25 | constructor ( | ||
26 | private userNotificationService: UserNotificationService, | ||
27 | private notifier: Notifier | ||
28 | ) { } | ||
29 | |||
30 | ngOnInit () { | ||
31 | this.componentPagination = { | ||
32 | currentPage: 1, | ||
33 | itemsPerPage: this.itemsPerPage, // Reset items per page, because of the @Input() variable | ||
34 | totalItems: null | ||
35 | } | ||
36 | |||
37 | this.loadMoreNotifications() | ||
38 | } | ||
39 | |||
40 | loadMoreNotifications () { | ||
41 | this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar) | ||
42 | .subscribe( | ||
43 | result => { | ||
44 | this.notifications = this.notifications.concat(result.data) | ||
45 | this.componentPagination.totalItems = result.total | ||
46 | }, | ||
47 | |||
48 | err => this.notifier.error(err.message) | ||
49 | ) | ||
50 | } | ||
51 | |||
52 | onNearOfBottom () { | ||
53 | if (this.infiniteScroll === false) return | ||
54 | |||
55 | this.componentPagination.currentPage++ | ||
56 | |||
57 | if (hasMoreItems(this.componentPagination)) { | ||
58 | this.loadMoreNotifications() | ||
59 | } | ||
60 | } | ||
61 | |||
62 | markAsRead (notification: UserNotification) { | ||
63 | if (notification.read) return | ||
64 | |||
65 | this.userNotificationService.markAsRead(notification) | ||
66 | .subscribe( | ||
67 | () => { | ||
68 | notification.read = true | ||
69 | }, | ||
70 | |||
71 | err => this.notifier.error(err.message) | ||
72 | ) | ||
73 | } | ||
74 | |||
75 | markAllAsRead () { | ||
76 | this.userNotificationService.markAllAsRead() | ||
77 | .subscribe( | ||
78 | () => { | ||
79 | for (const notification of this.notifications) { | ||
80 | notification.read = true | ||
81 | } | ||
82 | }, | ||
83 | |||
84 | err => this.notifier.error(err.message) | ||
85 | ) | ||
86 | } | ||
87 | } | ||
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 9819829fd..c15f1de8c 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -1,33 +1,8 @@ | |||
1 | import { | 1 | import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRight, UserRole, VideoChannel } from '../../../../../shared' |
2 | Account as AccountServerModel, | ||
3 | hasUserRight, | ||
4 | User as UserServerModel, | ||
5 | UserRight, | ||
6 | UserRole, | ||
7 | VideoChannel | ||
8 | } from '../../../../../shared' | ||
9 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | 2 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' |
10 | import { Account } from '@app/shared/account/account.model' | 3 | import { Account } from '@app/shared/account/account.model' |
11 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 4 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' |
12 | 5 | ||
13 | export type UserConstructorHash = { | ||
14 | id: number, | ||
15 | username: string, | ||
16 | email: string, | ||
17 | role: UserRole, | ||
18 | emailVerified?: boolean, | ||
19 | videoQuota?: number, | ||
20 | videoQuotaDaily?: number, | ||
21 | nsfwPolicy?: NSFWPolicyType, | ||
22 | webTorrentEnabled?: boolean, | ||
23 | autoPlayVideo?: boolean, | ||
24 | createdAt?: Date, | ||
25 | account?: AccountServerModel, | ||
26 | videoChannels?: VideoChannel[] | ||
27 | |||
28 | blocked?: boolean | ||
29 | blockedReason?: string | ||
30 | } | ||
31 | export class User implements UserServerModel { | 6 | export class User implements UserServerModel { |
32 | id: number | 7 | id: number |
33 | username: string | 8 | username: string |
@@ -35,8 +10,11 @@ export class User implements UserServerModel { | |||
35 | emailVerified: boolean | 10 | emailVerified: boolean |
36 | role: UserRole | 11 | role: UserRole |
37 | nsfwPolicy: NSFWPolicyType | 12 | nsfwPolicy: NSFWPolicyType |
13 | |||
38 | webTorrentEnabled: boolean | 14 | webTorrentEnabled: boolean |
39 | autoPlayVideo: boolean | 15 | autoPlayVideo: boolean |
16 | videosHistoryEnabled: boolean | ||
17 | |||
40 | videoQuota: number | 18 | videoQuota: number |
41 | videoQuotaDaily: number | 19 | videoQuotaDaily: number |
42 | account: Account | 20 | account: Account |
@@ -46,7 +24,9 @@ export class User implements UserServerModel { | |||
46 | blocked: boolean | 24 | blocked: boolean |
47 | blockedReason?: string | 25 | blockedReason?: string |
48 | 26 | ||
49 | constructor (hash: UserConstructorHash) { | 27 | notificationSettings?: UserNotificationSetting |
28 | |||
29 | constructor (hash: Partial<UserServerModel>) { | ||
50 | this.id = hash.id | 30 | this.id = hash.id |
51 | this.username = hash.username | 31 | this.username = hash.username |
52 | this.email = hash.email | 32 | this.email = hash.email |
@@ -57,11 +37,14 @@ export class User implements UserServerModel { | |||
57 | this.videoQuotaDaily = hash.videoQuotaDaily | 37 | this.videoQuotaDaily = hash.videoQuotaDaily |
58 | this.nsfwPolicy = hash.nsfwPolicy | 38 | this.nsfwPolicy = hash.nsfwPolicy |
59 | this.webTorrentEnabled = hash.webTorrentEnabled | 39 | this.webTorrentEnabled = hash.webTorrentEnabled |
40 | this.videosHistoryEnabled = hash.videosHistoryEnabled | ||
60 | this.autoPlayVideo = hash.autoPlayVideo | 41 | this.autoPlayVideo = hash.autoPlayVideo |
61 | this.createdAt = hash.createdAt | 42 | this.createdAt = hash.createdAt |
62 | this.blocked = hash.blocked | 43 | this.blocked = hash.blocked |
63 | this.blockedReason = hash.blockedReason | 44 | this.blockedReason = hash.blockedReason |
64 | 45 | ||
46 | this.notificationSettings = hash.notificationSettings | ||
47 | |||
65 | if (hash.account !== undefined) { | 48 | if (hash.account !== undefined) { |
66 | this.account = new Account(hash.account) | 49 | this.account = new Account(hash.account) |
67 | } | 50 | } |
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts index 61b7e1b98..b0b59ea0c 100644 --- a/client/src/app/shared/video-abuse/video-abuse.service.ts +++ b/client/src/app/shared/video-abuse/video-abuse.service.ts | |||
@@ -32,9 +32,7 @@ export class VideoAbuseService { | |||
32 | 32 | ||
33 | reportVideo (id: number, reason: string) { | 33 | reportVideo (id: number, reason: string) { |
34 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse' | 34 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse' |
35 | const body = { | 35 | const body = { reason } |
36 | reason | ||
37 | } | ||
38 | 36 | ||
39 | return this.authHttp.post(url, body) | 37 | return this.authHttp.post(url, body) |
40 | .pipe( | 38 | .pipe( |
diff --git a/client/src/app/shared/video-blacklist/video-blacklist.service.ts b/client/src/app/shared/video-blacklist/video-blacklist.service.ts index 7d39fd4f2..94e46d7c2 100644 --- a/client/src/app/shared/video-blacklist/video-blacklist.service.ts +++ b/client/src/app/shared/video-blacklist/video-blacklist.service.ts | |||
@@ -36,8 +36,11 @@ export class VideoBlacklistService { | |||
36 | ) | 36 | ) |
37 | } | 37 | } |
38 | 38 | ||
39 | blacklistVideo (videoId: number, reason?: string) { | 39 | blacklistVideo (videoId: number, reason: string, unfederate: boolean) { |
40 | const body = reason ? { reason } : {} | 40 | const body = { |
41 | unfederate, | ||
42 | reason | ||
43 | } | ||
41 | 44 | ||
42 | return this.authHttp.post(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist', body) | 45 | return this.authHttp.post(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist', body) |
43 | .pipe( | 46 | .pipe( |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index 29492351b..1f97bc389 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html | |||
@@ -1,8 +1,11 @@ | |||
1 | <div [ngClass]="{ 'margin-content': marginContent }"> | 1 | <div [ngClass]="{ 'margin-content': marginContent }"> |
2 | <div class="videos-header"> | 2 | <div class="videos-header"> |
3 | <div *ngIf="titlePage" class="title-page title-page-single"> | 3 | <div *ngIf="titlePage" class="title-page title-page-single"> |
4 | {{ titlePage }} | 4 | <div placement="bottom" [ngbTooltip]="titleTooltip" container="body"> |
5 | {{ titlePage }} | ||
6 | </div> | ||
5 | </div> | 7 | </div> |
8 | |||
6 | <my-feed [syndicationItems]="syndicationItems"></my-feed> | 9 | <my-feed [syndicationItems]="syndicationItems"></my-feed> |
7 | 10 | ||
8 | <div class="moderation-block" *ngIf="displayModerationBlock"> | 11 | <div class="moderation-block" *ngIf="displayModerationBlock"> |
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/video/abstract-video-list.scss index 9fb3fd4d6..292ede698 100644 --- a/client/src/app/shared/video/abstract-video-list.scss +++ b/client/src/app/shared/video/abstract-video-list.scss | |||
@@ -19,8 +19,8 @@ | |||
19 | 19 | ||
20 | my-feed { | 20 | my-feed { |
21 | display: inline-block; | 21 | display: inline-block; |
22 | position: relative; | ||
23 | top: 1px; | 22 | top: 1px; |
23 | min-width: 60px; | ||
24 | } | 24 | } |
25 | 25 | ||
26 | .moderation-block { | 26 | .moderation-block { |
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index 2d32dd6ad..b0633be4a 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts | |||
@@ -3,7 +3,6 @@ import { ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core' | |||
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { Location } from '@angular/common' | 4 | import { Location } from '@angular/common' |
5 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | 5 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' |
6 | import { NotificationsService } from 'angular2-notifications' | ||
7 | import { fromEvent, Observable, Subscription } from 'rxjs' | 6 | import { fromEvent, Observable, Subscription } from 'rxjs' |
8 | import { AuthService } from '../../core/auth' | 7 | import { AuthService } from '../../core/auth' |
9 | import { ComponentPagination } from '../rest/component-pagination.model' | 8 | import { ComponentPagination } from '../rest/component-pagination.model' |
@@ -13,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 12 | import { ScreenService } from '@app/shared/misc/screen.service' |
14 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | 13 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' |
15 | import { Syndication } from '@app/shared/video/syndication.model' | 14 | import { Syndication } from '@app/shared/video/syndication.model' |
15 | import { Notifier } from '@app/core' | ||
16 | 16 | ||
17 | export abstract class AbstractVideoList implements OnInit, OnDestroy { | 17 | export abstract class AbstractVideoList implements OnInit, OnDestroy { |
18 | private static LINES_PER_PAGE = 4 | 18 | private static LINES_PER_PAGE = 4 |
@@ -39,11 +39,12 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
39 | ownerDisplayType: OwnerDisplayType = 'account' | 39 | ownerDisplayType: OwnerDisplayType = 'account' |
40 | firstLoadedPage: number | 40 | firstLoadedPage: number |
41 | displayModerationBlock = false | 41 | displayModerationBlock = false |
42 | titleTooltip: string | ||
42 | 43 | ||
43 | protected baseVideoWidth = 215 | 44 | protected baseVideoWidth = 215 |
44 | protected baseVideoHeight = 205 | 45 | protected baseVideoHeight = 205 |
45 | 46 | ||
46 | protected abstract notificationsService: NotificationsService | 47 | protected abstract notifier: Notifier |
47 | protected abstract authService: AuthService | 48 | protected abstract authService: AuthService |
48 | protected abstract router: Router | 49 | protected abstract router: Router |
49 | protected abstract route: ActivatedRoute | 50 | protected abstract route: ActivatedRoute |
@@ -157,7 +158,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
157 | }, | 158 | }, |
158 | error => { | 159 | error => { |
159 | this.loadingPage[page] = false | 160 | this.loadingPage[page] = false |
160 | this.notificationsService.error(this.i18n('Error'), error.message) | 161 | this.notifier.error(error.message) |
161 | } | 162 | } |
162 | ) | 163 | ) |
163 | } | 164 | } |
diff --git a/client/src/app/shared/video/feed.component.html b/client/src/app/shared/video/feed.component.html index 16116ba88..f7624ec01 100644 --- a/client/src/app/shared/video/feed.component.html +++ b/client/src/app/shared/video/feed.component.html | |||
@@ -1,10 +1,11 @@ | |||
1 | <div class="video-feed"> | 1 | <div class="video-feed"> |
2 | <span | 2 | <my-global-icon |
3 | *ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom" | 3 | *ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom" |
4 | class="icon icon-syndication" role="button" | 4 | class="icon-syndication" role="button" iconName="syndication" |
5 | ></span> | 5 | > |
6 | </my-global-icon> | ||
6 | 7 | ||
7 | <ng-template #feedsList> | 8 | <ng-template #feedsList> |
8 | <a *ngFor="let item of syndicationItems" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a> | 9 | <a *ngFor="let item of syndicationItems" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a> |
9 | </ng-template> | 10 | </ng-template> |
10 | </div> \ No newline at end of file | 11 | </div> |
diff --git a/client/src/app/shared/video/feed.component.scss b/client/src/app/shared/video/feed.component.scss index 385764be0..ed1dc17d3 100644 --- a/client/src/app/shared/video/feed.component.scss +++ b/client/src/app/shared/video/feed.component.scss | |||
@@ -1,3 +1,4 @@ | |||
1 | @import '_variables'; | ||
1 | @import '_mixins'; | 2 | @import '_mixins'; |
2 | 3 | ||
3 | .video-feed { | 4 | .video-feed { |
@@ -6,14 +7,12 @@ | |||
6 | display: block; | 7 | display: block; |
7 | } | 8 | } |
8 | 9 | ||
9 | .icon { | 10 | my-global-icon { |
10 | @include icon(12px); | 11 | cursor: pointer; |
12 | width: 12px; | ||
13 | position: relative; | ||
14 | top: -2px; | ||
11 | 15 | ||
12 | &.icon-syndication { | 16 | @include apply-svg-color(var(--mainForegroundColor)) |
13 | position: relative; | ||
14 | top: -2px; | ||
15 | background-color: var(--mainForegroundColor); | ||
16 | mask-image: url('../../../assets/images/global/syndication.svg'); | ||
17 | } | ||
18 | } | 17 | } |
19 | } \ No newline at end of file | 18 | } |
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss index 895879adc..f44bdf9a9 100644 --- a/client/src/app/shared/video/video-miniature.component.scss +++ b/client/src/app/shared/video/video-miniature.component.scss | |||
@@ -50,10 +50,10 @@ | |||
50 | text-overflow: ellipsis; | 50 | text-overflow: ellipsis; |
51 | white-space: nowrap; | 51 | white-space: nowrap; |
52 | font-size: 13px; | 52 | font-size: 13px; |
53 | color: #585858; | 53 | color: $grey-foreground-color; |
54 | 54 | ||
55 | &:hover { | 55 | &:hover { |
56 | color: #303030; | 56 | color: $grey-foreground-hover-color; |
57 | } | 57 | } |
58 | } | 58 | } |
59 | } | 59 | } |
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index b92c96450..6ea83d13b 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts | |||
@@ -53,7 +53,7 @@ export class Video implements VideoServerModel { | |||
53 | displayName: string | 53 | displayName: string |
54 | url: string | 54 | url: string |
55 | host: string | 55 | host: string |
56 | avatar: Avatar | 56 | avatar?: Avatar |
57 | } | 57 | } |
58 | 58 | ||
59 | channel: { | 59 | channel: { |
@@ -63,7 +63,7 @@ export class Video implements VideoServerModel { | |||
63 | displayName: string | 63 | displayName: string |
64 | url: string | 64 | url: string |
65 | host: string | 65 | host: string |
66 | avatar: Avatar | 66 | avatar?: Avatar |
67 | } | 67 | } |
68 | 68 | ||
69 | userHistory?: { | 69 | userHistory?: { |
diff --git a/client/src/app/signup/signup.component.html b/client/src/app/signup/signup.component.html index 0207a166e..07d24b381 100644 --- a/client/src/app/signup/signup.component.html +++ b/client/src/app/signup/signup.component.html | |||
@@ -64,7 +64,7 @@ | |||
64 | </form> | 64 | </form> |
65 | 65 | ||
66 | <div> | 66 | <div> |
67 | <label for="email" i18n>Features found on this instance</label> | 67 | <label i18n>Features found on this instance</label> |
68 | <my-instance-features-table></my-instance-features-table> | 68 | <my-instance-features-table></my-instance-features-table> |
69 | </div> | 69 | </div> |
70 | </div> | 70 | </div> |
diff --git a/client/src/app/signup/signup.component.ts b/client/src/app/signup/signup.component.ts index 3341d4e09..13941ec79 100644 --- a/client/src/app/signup/signup.component.ts +++ b/client/src/app/signup/signup.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' |
3 | import { UserCreate } from '../../../../shared' | 3 | import { UserCreate } from '../../../../shared' |
4 | import { FormReactive, UserService, UserValidatorsService } from '../shared' | 4 | import { FormReactive, UserService, UserValidatorsService } from '../shared' |
5 | import { AuthService, RedirectService, ServerService } from '@app/core' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | 7 | ||
@@ -20,7 +19,7 @@ export class SignupComponent extends FormReactive implements OnInit { | |||
20 | protected formValidatorService: FormValidatorService, | 19 | protected formValidatorService: FormValidatorService, |
21 | private authService: AuthService, | 20 | private authService: AuthService, |
22 | private userValidatorsService: UserValidatorsService, | 21 | private userValidatorsService: UserValidatorsService, |
23 | private notificationsService: NotificationsService, | 22 | private notifier: Notifier, |
24 | private userService: UserService, | 23 | private userService: UserService, |
25 | private serverService: ServerService, | 24 | private serverService: ServerService, |
26 | private redirectService: RedirectService, | 25 | private redirectService: RedirectService, |
@@ -64,10 +63,7 @@ export class SignupComponent extends FormReactive implements OnInit { | |||
64 | this.authService.login(userCreate.username, userCreate.password) | 63 | this.authService.login(userCreate.username, userCreate.password) |
65 | .subscribe( | 64 | .subscribe( |
66 | () => { | 65 | () => { |
67 | this.notificationsService.success( | 66 | this.notifier.success(this.i18n('You are now logged in as {{username}}!', { username: userCreate.username })) |
68 | this.i18n('Success'), | ||
69 | this.i18n('You are now logged in as {{username}}!', { username: userCreate.username }) | ||
70 | ) | ||
71 | 67 | ||
72 | this.redirectService.redirectToHomepage() | 68 | this.redirectService.redirectToHomepage() |
73 | }, | 69 | }, |
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html index 30aefdbfc..19043eee6 100644 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html | |||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | <div class="modal-header"> | 4 | <div class="modal-header"> |
5 | <h4 i18n class="modal-title">Add caption</h4> | 5 | <h4 i18n class="modal-title">Add caption</h4> |
6 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 6 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
7 | </div> | 7 | </div> |
8 | 8 | ||
9 | <div class="modal-body"> | 9 | <div class="modal-body"> |
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 33c766d87..092c0e862 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 | |||
@@ -126,6 +126,7 @@ | |||
126 | ></my-peertube-checkbox> | 126 | ></my-peertube-checkbox> |
127 | 127 | ||
128 | <my-peertube-checkbox | 128 | <my-peertube-checkbox |
129 | *ngIf="waitTranscodingEnabled" | ||
129 | inputName="waitTranscoding" formControlName="waitTranscoding" | 130 | inputName="waitTranscoding" formControlName="waitTranscoding" |
130 | i18n-labelText labelText="Wait transcoding before publishing the video" | 131 | i18n-labelText labelText="Wait transcoding before publishing the video" |
131 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." | 132 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." |
@@ -142,7 +143,7 @@ | |||
142 | 143 | ||
143 | <div class="captions-header"> | 144 | <div class="captions-header"> |
144 | <a (click)="openAddCaptionModal()" class="create-caption"> | 145 | <a (click)="openAddCaptionModal()" class="create-caption"> |
145 | <span class="icon icon-add"></span> | 146 | <my-global-icon iconName="add"></my-global-icon> |
146 | <ng-container i18n>Add another caption</ng-container> | 147 | <ng-container i18n>Add another caption</ng-container> |
147 | </a> | 148 | </a> |
148 | </div> | 149 | </div> |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss index 25db8e8ed..bb775cb0a 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss | |||
@@ -23,10 +23,6 @@ my-peertube-checkbox { | |||
23 | display: block; | 23 | display: block; |
24 | } | 24 | } |
25 | 25 | ||
26 | input, select { | ||
27 | font-size: 15px | ||
28 | } | ||
29 | |||
30 | .label-tags + span { | 26 | .label-tags + span { |
31 | font-size: 15px; | 27 | font-size: 15px; |
32 | } | 28 | } |
@@ -42,7 +38,7 @@ my-peertube-checkbox { | |||
42 | text-align: right; | 38 | text-align: right; |
43 | 39 | ||
44 | .create-caption { | 40 | .create-caption { |
45 | @include create-button('../../../../assets/images/global/add.svg'); | 41 | @include create-button; |
46 | } | 42 | } |
47 | } | 43 | } |
48 | 44 | ||
@@ -100,13 +96,14 @@ my-peertube-checkbox { | |||
100 | display: inline-block; | 96 | display: inline-block; |
101 | margin-right: 25px; | 97 | margin-right: 25px; |
102 | 98 | ||
103 | color: #585858; | 99 | color: $grey-foreground-color; |
104 | font-size: 15px; | 100 | font-size: 15px; |
105 | } | 101 | } |
106 | 102 | ||
107 | .submit-button { | 103 | .submit-button { |
108 | @include peertube-button; | 104 | @include peertube-button; |
109 | @include orange-button; | 105 | @include orange-button; |
106 | @include button-with-icon(20px, 1px); | ||
110 | 107 | ||
111 | display: inline-block; | 108 | display: inline-block; |
112 | 109 | ||
@@ -119,16 +116,6 @@ my-peertube-checkbox { | |||
119 | color: inherit; | 116 | color: inherit; |
120 | font-weight: $font-semibold; | 117 | font-weight: $font-semibold; |
121 | } | 118 | } |
122 | |||
123 | .icon.icon-validate { | ||
124 | @include icon(20px); | ||
125 | |||
126 | cursor: inherit; | ||
127 | position: relative; | ||
128 | top: -1px; | ||
129 | margin-right: 4px; | ||
130 | background-image: url('../../../../assets/images/global/validate.svg'); | ||
131 | } | ||
132 | } | 119 | } |
133 | } | 120 | } |
134 | 121 | ||
@@ -176,10 +163,10 @@ p-calendar { | |||
176 | } | 163 | } |
177 | 164 | ||
178 | tag { | 165 | tag { |
179 | background-color: var(--inputColor) !important; | 166 | background-color: $grey-background-color !important; |
167 | color: #000 !important; | ||
180 | border-radius: 3px !important; | 168 | border-radius: 3px !important; |
181 | font-size: 15px !important; | 169 | font-size: 15px !important; |
182 | color: var(--mainForegroundColor) !important; | ||
183 | height: 30px !important; | 170 | height: 30px !important; |
184 | line-height: 30px !important; | 171 | line-height: 30px !important; |
185 | margin: 0 5px 0 0 !important; | 172 | margin: 0 5px 0 0 !important; |
@@ -202,7 +189,10 @@ p-calendar { | |||
202 | top: -1px; | 189 | top: -1px; |
203 | height: auto !important; | 190 | height: auto !important; |
204 | vertical-align: middle !important; | 191 | vertical-align: middle !important; |
205 | fill: #585858 !important; | 192 | |
193 | path { | ||
194 | fill: $grey-foreground-color !important; | ||
195 | } | ||
206 | } | 196 | } |
207 | 197 | ||
208 | &:hover { | 198 | &:hover { |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts index a56733e57..85e015901 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts | |||
@@ -2,7 +2,7 @@ import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' | |||
2 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' | 2 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared' | 4 | import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | import { ServerService } from '../../../core/server' | 6 | import { ServerService } from '../../../core/server' |
7 | import { VideoEdit } from '../../../shared/video/video-edit.model' | 7 | import { VideoEdit } from '../../../shared/video/video-edit.model' |
8 | import { map } from 'rxjs/operators' | 8 | import { map } from 'rxjs/operators' |
@@ -27,6 +27,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] |
28 | @Input() schedulePublicationPossible = true | 28 | @Input() schedulePublicationPossible = true |
29 | @Input() videoCaptions: VideoCaptionEdit[] = [] | 29 | @Input() videoCaptions: VideoCaptionEdit[] = [] |
30 | @Input() waitTranscodingEnabled = true | ||
30 | 31 | ||
31 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent | 32 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent |
32 | 33 | ||
@@ -58,7 +59,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
58 | private videoCaptionService: VideoCaptionService, | 59 | private videoCaptionService: VideoCaptionService, |
59 | private route: ActivatedRoute, | 60 | private route: ActivatedRoute, |
60 | private router: Router, | 61 | private router: Router, |
61 | private notificationsService: NotificationsService, | 62 | private notifier: Notifier, |
62 | private serverService: ServerService, | 63 | private serverService: ServerService, |
63 | private i18nPrimengCalendarService: I18nPrimengCalendarService | 64 | private i18nPrimengCalendarService: I18nPrimengCalendarService |
64 | ) { | 65 | ) { |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html index 11a81ad66..28eb143c9 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> | 1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> |
2 | <div class="import-video-torrent"> | 2 | <div class="first-step-block"> |
3 | <div class="icon icon-upload"></div> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="button-file"> | 5 | <div class="button-file"> |
6 | <span i18n>Select the torrent to import</span> | 6 | <span i18n>Select the torrent to import</span> |
@@ -66,7 +66,7 @@ | |||
66 | (click)="updateSecondStep()" | 66 | (click)="updateSecondStep()" |
67 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" | 67 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" |
68 | > | 68 | > |
69 | <span class="icon icon-validate"></span> | 69 | <my-global-icon iconName="validate"></my-global-icon> |
70 | <input type="button" i18n-value value="Update" /> | 70 | <input type="button" i18n-value value="Update" /> |
71 | </div> | 71 | </div> |
72 | </div> | 72 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss index 00626cd7b..6d59ed834 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss | |||
@@ -1,45 +1,7 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | $width-size: 190px; | 4 | .first-step-block { |
5 | |||
6 | .peertube-select-container { | ||
7 | @include peertube-select-container($width-size); | ||
8 | } | ||
9 | |||
10 | .alert.alert-danger { | ||
11 | text-align: center; | ||
12 | |||
13 | & > div { | ||
14 | font-weight: $font-semibold; | ||
15 | } | ||
16 | } | ||
17 | |||
18 | .import-video-torrent { | ||
19 | display: flex; | ||
20 | flex-direction: column; | ||
21 | align-items: center; | ||
22 | |||
23 | .icon.icon-upload { | ||
24 | @include icon(90px); | ||
25 | margin-bottom: 25px; | ||
26 | cursor: default; | ||
27 | |||
28 | background-image: url('../../../../assets/images/video/upload.svg'); | ||
29 | } | ||
30 | |||
31 | .button-file { | ||
32 | @include peertube-button-file(auto); | ||
33 | |||
34 | min-width: 190px; | ||
35 | } | ||
36 | |||
37 | .button-file-extension { | ||
38 | display: block; | ||
39 | font-size: 12px; | ||
40 | margin-top: 5px; | ||
41 | } | ||
42 | |||
43 | .torrent-or-magnet { | 5 | .torrent-or-magnet { |
44 | margin: 10px 0; | 6 | margin: 10px 0; |
45 | } | 7 | } |
@@ -47,19 +9,6 @@ $width-size: 190px; | |||
47 | .form-group-magnet-uri { | 9 | .form-group-magnet-uri { |
48 | margin-bottom: 40px; | 10 | margin-bottom: 40px; |
49 | } | 11 | } |
50 | |||
51 | input[type=text] { | ||
52 | @include peertube-input-text($width-size); | ||
53 | display: block; | ||
54 | } | ||
55 | |||
56 | input[type=button] { | ||
57 | @include peertube-button; | ||
58 | @include orange-button; | ||
59 | |||
60 | width: $width-size; | ||
61 | margin-top: 30px; | ||
62 | } | ||
63 | } | 12 | } |
64 | 13 | ||
65 | 14 | ||
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts index 13776ae36..307806bb9 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' | 3 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' |
5 | import { AuthService, ServerService } from '../../../core' | 4 | import { AuthService, Notifier, ServerService } from '../../../core' |
6 | import { VideoService } from '../../../shared/video/video.service' | 5 | import { VideoService } from '../../../shared/video/video.service' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { LoadingBarService } from '@ngx-loading-bar/core' | 7 | import { LoadingBarService } from '@ngx-loading-bar/core' |
@@ -19,7 +18,8 @@ import { scrollToTop } from '@app/shared/misc/utils' | |||
19 | templateUrl: './video-import-torrent.component.html', | 18 | templateUrl: './video-import-torrent.component.html', |
20 | styleUrls: [ | 19 | styleUrls: [ |
21 | '../shared/video-edit.component.scss', | 20 | '../shared/video-edit.component.scss', |
22 | './video-import-torrent.component.scss' | 21 | './video-import-torrent.component.scss', |
22 | './video-send.scss' | ||
23 | ] | 23 | ] |
24 | }) | 24 | }) |
25 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 25 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { |
@@ -41,7 +41,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
41 | constructor ( | 41 | constructor ( |
42 | protected formValidatorService: FormValidatorService, | 42 | protected formValidatorService: FormValidatorService, |
43 | protected loadingBar: LoadingBarService, | 43 | protected loadingBar: LoadingBarService, |
44 | protected notificationsService: NotificationsService, | 44 | protected notifier: Notifier, |
45 | protected authService: AuthService, | 45 | protected authService: AuthService, |
46 | protected serverService: ServerService, | 46 | protected serverService: ServerService, |
47 | protected videoService: VideoService, | 47 | protected videoService: VideoService, |
@@ -107,7 +107,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
107 | this.loadingBar.complete() | 107 | this.loadingBar.complete() |
108 | this.isImportingVideo = false | 108 | this.isImportingVideo = false |
109 | this.firstStepError.emit() | 109 | this.firstStepError.emit() |
110 | this.notificationsService.error(this.i18n('Error'), err.message) | 110 | this.notifier.error(err.message) |
111 | } | 111 | } |
112 | ) | 112 | ) |
113 | } | 113 | } |
@@ -126,7 +126,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
126 | .subscribe( | 126 | .subscribe( |
127 | () => { | 127 | () => { |
128 | this.isUpdatingVideo = false | 128 | this.isUpdatingVideo = false |
129 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video to import updated.')) | 129 | this.notifier.success(this.i18n('Video to import updated.')) |
130 | 130 | ||
131 | this.router.navigate([ '/my-account', 'video-imports' ]) | 131 | this.router.navigate([ '/my-account', 'video-imports' ]) |
132 | }, | 132 | }, |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html index 533446672..3550c3585 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> | 1 | <div *ngIf="!hasImportedVideo" class="upload-video-container"> |
2 | <div class="import-video-url"> | 2 | <div class="first-step-block"> |
3 | <div class="icon icon-upload"></div> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <label i18n for="targetUrl">URL</label> | 6 | <label i18n for="targetUrl">URL</label> |
@@ -59,7 +59,7 @@ | |||
59 | (click)="updateSecondStep()" | 59 | (click)="updateSecondStep()" |
60 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" | 60 | [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }" |
61 | > | 61 | > |
62 | <span class="icon icon-validate"></span> | 62 | <my-global-icon iconName="validate"></my-global-icon> |
63 | <input type="button" i18n-value value="Update" /> | 63 | <input type="button" i18n-value value="Update" /> |
64 | </div> | 64 | </div> |
65 | </div> | 65 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts index 9cdface75..257c6e5db 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' | 3 | import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos' |
5 | import { AuthService, ServerService } from '../../../core' | 4 | import { AuthService, Notifier, ServerService } from '../../../core' |
6 | import { VideoService } from '../../../shared/video/video.service' | 5 | import { VideoService } from '../../../shared/video/video.service' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { LoadingBarService } from '@ngx-loading-bar/core' | 7 | import { LoadingBarService } from '@ngx-loading-bar/core' |
@@ -19,7 +18,7 @@ import { scrollToTop } from '@app/shared/misc/utils' | |||
19 | templateUrl: './video-import-url.component.html', | 18 | templateUrl: './video-import-url.component.html', |
20 | styleUrls: [ | 19 | styleUrls: [ |
21 | '../shared/video-edit.component.scss', | 20 | '../shared/video-edit.component.scss', |
22 | './video-import-url.component.scss' | 21 | './video-send.scss' |
23 | ] | 22 | ] |
24 | }) | 23 | }) |
25 | export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 24 | export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate { |
@@ -40,7 +39,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
40 | constructor ( | 39 | constructor ( |
41 | protected formValidatorService: FormValidatorService, | 40 | protected formValidatorService: FormValidatorService, |
42 | protected loadingBar: LoadingBarService, | 41 | protected loadingBar: LoadingBarService, |
43 | protected notificationsService: NotificationsService, | 42 | protected notifier: Notifier, |
44 | protected authService: AuthService, | 43 | protected authService: AuthService, |
45 | protected serverService: ServerService, | 44 | protected serverService: ServerService, |
46 | protected videoService: VideoService, | 45 | protected videoService: VideoService, |
@@ -99,7 +98,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
99 | this.loadingBar.complete() | 98 | this.loadingBar.complete() |
100 | this.isImportingVideo = false | 99 | this.isImportingVideo = false |
101 | this.firstStepError.emit() | 100 | this.firstStepError.emit() |
102 | this.notificationsService.error(this.i18n('Error'), err.message) | 101 | this.notifier.error(err.message) |
103 | } | 102 | } |
104 | ) | 103 | ) |
105 | } | 104 | } |
@@ -118,7 +117,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
118 | .subscribe( | 117 | .subscribe( |
119 | () => { | 118 | () => { |
120 | this.isUpdatingVideo = false | 119 | this.isUpdatingVideo = false |
121 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video to import updated.')) | 120 | this.notifier.success(this.i18n('Video to import updated.')) |
122 | 121 | ||
123 | this.router.navigate([ '/my-account', 'video-imports' ]) | 122 | this.router.navigate([ '/my-account', 'video-imports' ]) |
124 | }, | 123 | }, |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-send.scss index e907edc70..8769dd302 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss +++ b/client/src/app/videos/+video-edit/video-add-components/video-send.scss | |||
@@ -3,10 +3,6 @@ | |||
3 | 3 | ||
4 | $width-size: 190px; | 4 | $width-size: 190px; |
5 | 5 | ||
6 | .peertube-select-container { | ||
7 | @include peertube-select-container($width-size); | ||
8 | } | ||
9 | |||
10 | .alert.alert-danger { | 6 | .alert.alert-danger { |
11 | text-align: center; | 7 | text-align: center; |
12 | 8 | ||
@@ -15,17 +11,20 @@ $width-size: 190px; | |||
15 | } | 11 | } |
16 | } | 12 | } |
17 | 13 | ||
18 | .import-video-url { | 14 | .first-step-block { |
19 | display: flex; | 15 | display: flex; |
20 | flex-direction: column; | 16 | flex-direction: column; |
21 | align-items: center; | 17 | align-items: center; |
22 | 18 | ||
23 | .icon.icon-upload { | 19 | .upload-icon { |
24 | @include icon(90px); | 20 | width: 90px; |
25 | margin-bottom: 25px; | 21 | margin-bottom: 25px; |
26 | cursor: default; | ||
27 | 22 | ||
28 | background-image: url('../../../../assets/images/video/upload.svg'); | 23 | @include apply-svg-color(#C6C6C6); |
24 | } | ||
25 | |||
26 | .peertube-select-container { | ||
27 | @include peertube-select-container($width-size); | ||
29 | } | 28 | } |
30 | 29 | ||
31 | input[type=text] { | 30 | input[type=text] { |
@@ -40,6 +39,16 @@ $width-size: 190px; | |||
40 | width: $width-size; | 39 | width: $width-size; |
41 | margin-top: 30px; | 40 | margin-top: 30px; |
42 | } | 41 | } |
43 | } | ||
44 | 42 | ||
43 | .button-file { | ||
44 | @include peertube-button-file(auto); | ||
45 | 45 | ||
46 | min-width: 190px; | ||
47 | } | ||
48 | |||
49 | .button-file-extension { | ||
50 | display: block; | ||
51 | font-size: 12px; | ||
52 | margin-top: 5px; | ||
53 | } | ||
54 | } | ||
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-send.ts b/client/src/app/videos/+video-edit/video-add-components/video-send.ts index 71d2544d8..580c123a0 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-send.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-send.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { EventEmitter, OnInit } from '@angular/core' | 1 | import { EventEmitter, OnInit } from '@angular/core' |
2 | import { LoadingBarService } from '@ngx-loading-bar/core' | 2 | import { LoadingBarService } from '@ngx-loading-bar/core' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { AuthService, Notifier, ServerService } from '@app/core' |
4 | import { catchError, switchMap, tap } from 'rxjs/operators' | 4 | import { catchError, switchMap, tap } from 'rxjs/operators' |
5 | import { FormReactive } from '@app/shared' | 5 | import { FormReactive } from '@app/shared' |
6 | import { VideoConstant, VideoPrivacy } from '../../../../../../shared' | 6 | import { VideoConstant, VideoPrivacy } from '../../../../../../shared' |
7 | import { AuthService, ServerService } from '@app/core' | ||
8 | import { VideoService } from '@app/shared/video/video.service' | 7 | import { VideoService } from '@app/shared/video/video.service' |
9 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | 8 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' |
10 | import { VideoCaptionService } from '@app/shared/video-caption' | 9 | import { VideoCaptionService } from '@app/shared/video-caption' |
@@ -25,7 +24,7 @@ export abstract class VideoSend extends FormReactive implements OnInit { | |||
25 | protected abstract readonly DEFAULT_VIDEO_PRIVACY: VideoPrivacy | 24 | protected abstract readonly DEFAULT_VIDEO_PRIVACY: VideoPrivacy |
26 | 25 | ||
27 | protected loadingBar: LoadingBarService | 26 | protected loadingBar: LoadingBarService |
28 | protected notificationsService: NotificationsService | 27 | protected notifier: Notifier |
29 | protected authService: AuthService | 28 | protected authService: AuthService |
30 | protected serverService: ServerService | 29 | protected serverService: ServerService |
31 | protected videoService: VideoService | 30 | protected videoService: VideoService |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html index a09f54dfc..b252cd60a 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html | |||
@@ -1,12 +1,12 @@ | |||
1 | <div *ngIf="!isUploadingVideo" class="upload-video-container"> | 1 | <div *ngIf="!isUploadingVideo" class="upload-video-container"> |
2 | <div class="upload-video"> | 2 | <div class="first-step-block"> |
3 | <div class="icon icon-upload"></div> | 3 | <my-global-icon class="upload-icon" iconName="upload"></my-global-icon> |
4 | 4 | ||
5 | <div class="button-file"> | 5 | <div class="button-file"> |
6 | <span i18n>Select the file to upload</span> | 6 | <span i18n>Select the file to upload</span> |
7 | <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" /> | 7 | <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" /> |
8 | </div> | 8 | </div> |
9 | <span class="button-file-extension">(.mp4, .webm, .ogv)</span> | 9 | <span class="button-file-extension">({{ videoExtensions }})</span> |
10 | 10 | ||
11 | <div class="form-group form-group-channel"> | 11 | <div class="form-group form-group-channel"> |
12 | <label i18n for="first-step-channel">Channel</label> | 12 | <label i18n for="first-step-channel">Channel</label> |
@@ -42,11 +42,16 @@ | |||
42 | {{ error }} | 42 | {{ error }} |
43 | </div> | 43 | </div> |
44 | 44 | ||
45 | <div *ngIf="videoUploaded && !error" class="alert alert-info" i18n> | ||
46 | Congratulations! Your video is now available in your private library. | ||
47 | </div> | ||
48 | |||
45 | <!-- Hidden because we want to load the component --> | 49 | <!-- Hidden because we want to load the component --> |
46 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> | 50 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> |
47 | <my-video-edit | 51 | <my-video-edit |
48 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" | 52 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" |
49 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 53 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
54 | [waitTranscodingEnabled]="waitTranscodingEnabled" | ||
50 | ></my-video-edit> | 55 | ></my-video-edit> |
51 | 56 | ||
52 | <div class="submit-container"> | 57 | <div class="submit-container"> |
@@ -56,7 +61,7 @@ | |||
56 | (click)="updateSecondStep()" | 61 | (click)="updateSecondStep()" |
57 | [ngClass]="{ disabled: isPublishingButtonDisabled() }" | 62 | [ngClass]="{ disabled: isPublishingButtonDisabled() }" |
58 | > | 63 | > |
59 | <span class="icon icon-validate"></span> | 64 | <my-global-icon iconName="validate"></my-global-icon> |
60 | <input [disabled]="isPublishingButtonDisabled()" type="button" i18n-value value="Publish" /> | 65 | <input [disabled]="isPublishingButtonDisabled()" type="button" i18n-value value="Publish" /> |
61 | </div> | 66 | </div> |
62 | </div> | 67 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss index cf1725ef9..8adf8f169 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss | |||
@@ -1,47 +1,9 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | .peertube-select-container { | 4 | .first-step-block .form-group-channel { |
5 | @include peertube-select-container(190px); | 5 | margin-bottom: 20px; |
6 | } | 6 | margin-top: 35px; |
7 | |||
8 | .alert.alert-danger { | ||
9 | text-align: center; | ||
10 | |||
11 | & > div { | ||
12 | font-weight: $font-semibold; | ||
13 | } | ||
14 | } | ||
15 | |||
16 | .upload-video { | ||
17 | display: flex; | ||
18 | flex-direction: column; | ||
19 | align-items: center; | ||
20 | |||
21 | .form-group-channel { | ||
22 | margin-bottom: 20px; | ||
23 | margin-top: 35px; | ||
24 | } | ||
25 | |||
26 | .icon.icon-upload { | ||
27 | @include icon(90px); | ||
28 | margin-bottom: 25px; | ||
29 | cursor: default; | ||
30 | |||
31 | background-image: url('../../../../assets/images/video/upload.svg'); | ||
32 | } | ||
33 | |||
34 | .button-file { | ||
35 | @include peertube-button-file(auto); | ||
36 | |||
37 | min-width: 190px; | ||
38 | } | ||
39 | |||
40 | .button-file-extension { | ||
41 | display: block; | ||
42 | font-size: 12px; | ||
43 | margin-top: 5px; | ||
44 | } | ||
45 | } | 7 | } |
46 | 8 | ||
47 | .upload-progress-cancel { | 9 | .upload-progress-cancel { |
@@ -54,9 +16,7 @@ | |||
54 | 16 | ||
55 | /deep/ .ui-progressbar { | 17 | /deep/ .ui-progressbar { |
56 | font-size: 15px !important; | 18 | font-size: 15px !important; |
57 | color: #fff !important; | ||
58 | height: 30px !important; | 19 | height: 30px !important; |
59 | line-height: 30px !important; | ||
60 | border-radius: 3px !important; | 20 | border-radius: 3px !important; |
61 | background-color: rgba(11, 204, 41, 0.16) !important; | 21 | background-color: rgba(11, 204, 41, 0.16) !important; |
62 | 22 | ||
@@ -68,6 +28,8 @@ | |||
68 | text-align: left; | 28 | text-align: left; |
69 | padding-left: 18px; | 29 | padding-left: 18px; |
70 | margin-top: 0 !important; | 30 | margin-top: 0 !important; |
31 | color: #fff !important; | ||
32 | line-height: 30px !important; | ||
71 | } | 33 | } |
72 | } | 34 | } |
73 | 35 | ||
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 3fcb71ac3..e4d54b654 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 | |||
@@ -2,11 +2,10 @@ import { HttpEventType, HttpResponse } from '@angular/common/http' | |||
2 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' | 2 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' |
3 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
4 | import { LoadingBarService } from '@ngx-loading-bar/core' | 4 | import { LoadingBarService } from '@ngx-loading-bar/core' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { BytesPipe } from 'ngx-pipes' | 5 | import { BytesPipe } from 'ngx-pipes' |
7 | import { Subscription } from 'rxjs' | 6 | import { Subscription } from 'rxjs' |
8 | import { VideoPrivacy } from '../../../../../../shared/models/videos' | 7 | import { VideoPrivacy } from '../../../../../../shared/models/videos' |
9 | import { AuthService, ServerService } from '../../../core' | 8 | import { AuthService, Notifier, ServerService } from '../../../core' |
10 | import { VideoEdit } from '../../../shared/video/video-edit.model' | 9 | import { VideoEdit } from '../../../shared/video/video-edit.model' |
11 | import { VideoService } from '../../../shared/video/video.service' | 10 | import { VideoService } from '../../../shared/video/video.service' |
12 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -21,7 +20,8 @@ import { scrollToTop } from '@app/shared/misc/utils' | |||
21 | templateUrl: './video-upload.component.html', | 20 | templateUrl: './video-upload.component.html', |
22 | styleUrls: [ | 21 | styleUrls: [ |
23 | '../shared/video-edit.component.scss', | 22 | '../shared/video-edit.component.scss', |
24 | './video-upload.component.scss' | 23 | './video-upload.component.scss', |
24 | './video-send.scss' | ||
25 | ] | 25 | ] |
26 | }) | 26 | }) |
27 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { | 27 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { |
@@ -44,6 +44,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
44 | id: 0, | 44 | id: 0, |
45 | uuid: '' | 45 | uuid: '' |
46 | } | 46 | } |
47 | waitTranscodingEnabled = true | ||
47 | 48 | ||
48 | error: string | 49 | error: string |
49 | 50 | ||
@@ -52,7 +53,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
52 | constructor ( | 53 | constructor ( |
53 | protected formValidatorService: FormValidatorService, | 54 | protected formValidatorService: FormValidatorService, |
54 | protected loadingBar: LoadingBarService, | 55 | protected loadingBar: LoadingBarService, |
55 | protected notificationsService: NotificationsService, | 56 | protected notifier: Notifier, |
56 | protected authService: AuthService, | 57 | protected authService: AuthService, |
57 | protected serverService: ServerService, | 58 | protected serverService: ServerService, |
58 | protected videoService: VideoService, | 59 | protected videoService: VideoService, |
@@ -109,7 +110,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
109 | this.isUploadingVideo = false | 110 | this.isUploadingVideo = false |
110 | this.videoUploadPercents = 0 | 111 | this.videoUploadPercents = 0 |
111 | this.videoUploadObservable = null | 112 | this.videoUploadObservable = null |
112 | this.notificationsService.info(this.i18n('Info'), this.i18n('Upload cancelled')) | 113 | this.notifier.info(this.i18n('Upload cancelled')) |
113 | } | 114 | } |
114 | } | 115 | } |
115 | 116 | ||
@@ -117,12 +118,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
117 | const videofile = this.videofileInput.nativeElement.files[0] | 118 | const videofile = this.videofileInput.nativeElement.files[0] |
118 | if (!videofile) return | 119 | if (!videofile) return |
119 | 120 | ||
120 | // Cannot upload videos > 8GB for now | 121 | // Check global user quota |
121 | if (videofile.size > 8 * 1024 * 1024 * 1024) { | ||
122 | this.notificationsService.error(this.i18n('Error'), this.i18n('We are sorry but PeerTube cannot handle videos > 8GB')) | ||
123 | return | ||
124 | } | ||
125 | |||
126 | const bytePipes = new BytesPipe() | 122 | const bytePipes = new BytesPipe() |
127 | const videoQuota = this.authService.getUser().videoQuota | 123 | const videoQuota = this.authService.getUser().videoQuota |
128 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { | 124 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { |
@@ -134,10 +130,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
134 | videoQuota: bytePipes.transform(videoQuota, 0) | 130 | videoQuota: bytePipes.transform(videoQuota, 0) |
135 | } | 131 | } |
136 | ) | 132 | ) |
137 | this.notificationsService.error(this.i18n('Error'), msg) | 133 | this.notifier.error(msg) |
138 | return | 134 | return |
139 | } | 135 | } |
140 | 136 | ||
137 | // Check daily user quota | ||
141 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily | 138 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily |
142 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { | 139 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { |
143 | const msg = this.i18n( | 140 | const msg = this.i18n( |
@@ -148,10 +145,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
148 | quotaDaily: bytePipes.transform(videoQuotaDaily, 0) | 145 | quotaDaily: bytePipes.transform(videoQuotaDaily, 0) |
149 | } | 146 | } |
150 | ) | 147 | ) |
151 | this.notificationsService.error(this.i18n('Error'), msg) | 148 | this.notifier.error(msg) |
152 | return | 149 | return |
153 | } | 150 | } |
154 | 151 | ||
152 | // Build name field | ||
155 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') | 153 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') |
156 | let name: string | 154 | let name: string |
157 | 155 | ||
@@ -159,6 +157,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
159 | if (nameWithoutExtension.length < 3) name = videofile.name | 157 | if (nameWithoutExtension.length < 3) name = videofile.name |
160 | else name = nameWithoutExtension | 158 | else name = nameWithoutExtension |
161 | 159 | ||
160 | // Force user to wait transcoding for unsupported video types in web browsers | ||
161 | if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) { | ||
162 | this.waitTranscodingEnabled = false | ||
163 | } | ||
164 | |||
162 | const privacy = this.firstStepPrivacyId.toString() | 165 | const privacy = this.firstStepPrivacyId.toString() |
163 | const nsfw = false | 166 | const nsfw = false |
164 | const waitTranscoding = true | 167 | const waitTranscoding = true |
@@ -206,7 +209,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
206 | this.videoUploadPercents = 0 | 209 | this.videoUploadPercents = 0 |
207 | this.videoUploadObservable = null | 210 | this.videoUploadObservable = null |
208 | this.firstStepError.emit() | 211 | this.firstStepError.emit() |
209 | this.notificationsService.error(this.i18n('Error'), err.message) | 212 | this.notifier.error(err.message) |
210 | } | 213 | } |
211 | ) | 214 | ) |
212 | } | 215 | } |
@@ -235,7 +238,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
235 | this.isUpdatingVideo = false | 238 | this.isUpdatingVideo = false |
236 | this.isUploadingVideo = false | 239 | this.isUploadingVideo = false |
237 | 240 | ||
238 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video published.')) | 241 | this.notifier.success(this.i18n('Video published.')) |
239 | this.router.navigate([ '/videos/watch', video.uuid ]) | 242 | this.router.navigate([ '/videos/watch', video.uuid ]) |
240 | }, | 243 | }, |
241 | 244 | ||
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 57a9d0ca7..01fdfcb66 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, ViewChild } from '@angular/core' | 1 | import { Component, HostListener, ViewChild } from '@angular/core' |
2 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' | 2 | import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' |
3 | import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' | 3 | import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' |
4 | import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' | 4 | import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' |
@@ -32,7 +32,17 @@ export class VideoAddComponent implements CanComponentDeactivate { | |||
32 | this.secondStepType = undefined | 32 | this.secondStepType = undefined |
33 | } | 33 | } |
34 | 34 | ||
35 | canDeactivate () { | 35 | @HostListener('window:beforeunload', [ '$event' ]) |
36 | onUnload (event: any) { | ||
37 | const { text, canDeactivate } = this.canDeactivate() | ||
38 | |||
39 | if (canDeactivate) return | ||
40 | |||
41 | event.returnValue = text | ||
42 | return text | ||
43 | } | ||
44 | |||
45 | canDeactivate (): { canDeactivate: boolean, text?: string} { | ||
36 | if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate() | 46 | if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate() |
37 | if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() | 47 | if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() |
38 | if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() | 48 | if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() |
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html index 9242c30a0..4992bb369 100644 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ b/client/src/app/videos/+video-edit/video-update.component.html | |||
@@ -8,12 +8,12 @@ | |||
8 | <my-video-edit | 8 | <my-video-edit |
9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" | 9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" |
10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
11 | [videoCaptions]="videoCaptions" | 11 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" |
12 | ></my-video-edit> | 12 | ></my-video-edit> |
13 | 13 | ||
14 | <div class="submit-container"> | 14 | <div class="submit-container"> |
15 | <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }"> | 15 | <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }"> |
16 | <span class="icon icon-validate"></span> | 16 | <my-global-icon iconName="validate"></my-global-icon> |
17 | <input type="button" i18n-value value="Update" /> | 17 | <input type="button" i18n-value value="Update" /> |
18 | </div> | 18 | </div> |
19 | </div> | 19 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts index 3a0f3a39a..9e849014e 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { map, switchMap } from 'rxjs/operators' | 1 | import { map, switchMap } from 'rxjs/operators' |
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, HostListener, OnInit } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { LoadingBarService } from '@ngx-loading-bar/core' | 4 | import { LoadingBarService } from '@ngx-loading-bar/core' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos' | 6 | import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos' |
7 | import { ServerService } from '../../core' | 7 | import { ServerService } from '../../core' |
8 | import { FormReactive } from '../../shared' | 8 | import { FormReactive } from '../../shared' |
@@ -12,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
12 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 12 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
13 | import { VideoCaptionService } from '@app/shared/video-caption' | 13 | import { VideoCaptionService } from '@app/shared/video-caption' |
14 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | 14 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' |
15 | import { VideoDetails } from '@app/shared/video/video-details.model' | ||
15 | 16 | ||
16 | @Component({ | 17 | @Component({ |
17 | selector: 'my-videos-update', | 18 | selector: 'my-videos-update', |
@@ -26,6 +27,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
26 | userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | userVideoChannels: { id: number, label: string, support: string }[] = [] |
27 | schedulePublicationPossible = false | 28 | schedulePublicationPossible = false |
28 | videoCaptions: VideoCaptionEdit[] = [] | 29 | videoCaptions: VideoCaptionEdit[] = [] |
30 | waitTranscodingEnabled = true | ||
29 | 31 | ||
30 | private updateDone = false | 32 | private updateDone = false |
31 | 33 | ||
@@ -33,7 +35,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
33 | protected formValidatorService: FormValidatorService, | 35 | protected formValidatorService: FormValidatorService, |
34 | private route: ActivatedRoute, | 36 | private route: ActivatedRoute, |
35 | private router: Router, | 37 | private router: Router, |
36 | private notificationsService: NotificationsService, | 38 | private notifier: Notifier, |
37 | private serverService: ServerService, | 39 | private serverService: ServerService, |
38 | private videoService: VideoService, | 40 | private videoService: VideoService, |
39 | private loadingBar: LoadingBarService, | 41 | private loadingBar: LoadingBarService, |
@@ -65,25 +67,42 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
65 | 67 | ||
66 | this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) | 68 | this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) |
67 | 69 | ||
70 | const videoFiles = (video as VideoDetails).files | ||
71 | if (videoFiles.length > 1) { // Already transcoded | ||
72 | this.waitTranscodingEnabled = false | ||
73 | } | ||
74 | |||
68 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout | 75 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout |
69 | setTimeout(() => this.hydrateFormFromVideo()) | 76 | setTimeout(() => this.hydrateFormFromVideo()) |
70 | }, | 77 | }, |
71 | 78 | ||
72 | err => { | 79 | err => { |
73 | console.error(err) | 80 | console.error(err) |
74 | this.notificationsService.error(this.i18n('Error'), err.message) | 81 | this.notifier.error(err.message) |
75 | } | 82 | } |
76 | ) | 83 | ) |
77 | } | 84 | } |
78 | 85 | ||
79 | canDeactivate () { | 86 | @HostListener('window:beforeunload', [ '$event' ]) |
87 | onUnload (event: any) { | ||
88 | const { text, canDeactivate } = this.canDeactivate() | ||
89 | |||
90 | if (canDeactivate) return | ||
91 | |||
92 | event.returnValue = text | ||
93 | return text | ||
94 | } | ||
95 | |||
96 | canDeactivate (): { canDeactivate: boolean, text?: string } { | ||
80 | if (this.updateDone === true) return { canDeactivate: true } | 97 | if (this.updateDone === true) return { canDeactivate: true } |
81 | 98 | ||
99 | const text = this.i18n('You have unsaved changes! If you leave, your changes will be lost.') | ||
100 | |||
82 | for (const caption of this.videoCaptions) { | 101 | for (const caption of this.videoCaptions) { |
83 | if (caption.action) return { canDeactivate: false } | 102 | if (caption.action) return { canDeactivate: false, text } |
84 | } | 103 | } |
85 | 104 | ||
86 | return { canDeactivate: this.formChanged === false } | 105 | return { canDeactivate: this.formChanged === false, text } |
87 | } | 106 | } |
88 | 107 | ||
89 | checkForm () { | 108 | checkForm () { |
@@ -114,14 +133,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
114 | this.updateDone = true | 133 | this.updateDone = true |
115 | this.isUpdatingVideo = false | 134 | this.isUpdatingVideo = false |
116 | this.loadingBar.complete() | 135 | this.loadingBar.complete() |
117 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.')) | 136 | this.notifier.success(this.i18n('Video updated.')) |
118 | this.router.navigate([ '/videos/watch', this.video.uuid ]) | 137 | this.router.navigate([ '/videos/watch', this.video.uuid ]) |
119 | }, | 138 | }, |
120 | 139 | ||
121 | err => { | 140 | err => { |
122 | this.loadingBar.complete() | 141 | this.loadingBar.complete() |
123 | this.isUpdatingVideo = false | 142 | this.isUpdatingVideo = false |
124 | this.notificationsService.error(this.i18n('Error'), err.message) | 143 | this.notifier.error(err.message) |
125 | console.error(err) | 144 | console.error(err) |
126 | } | 145 | } |
127 | ) | 146 | ) |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts index 6db0eb55d..fd85c28f2 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { Observable } from 'rxjs' | 4 | import { Observable } from 'rxjs' |
5 | import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' | 5 | import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' |
6 | import { FormReactive } from '../../../shared' | 6 | import { FormReactive } from '../../../shared' |
@@ -36,7 +36,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
36 | constructor ( | 36 | constructor ( |
37 | protected formValidatorService: FormValidatorService, | 37 | protected formValidatorService: FormValidatorService, |
38 | private videoCommentValidatorsService: VideoCommentValidatorsService, | 38 | private videoCommentValidatorsService: VideoCommentValidatorsService, |
39 | private notificationsService: NotificationsService, | 39 | private notifier: Notifier, |
40 | private videoCommentService: VideoCommentService, | 40 | private videoCommentService: VideoCommentService, |
41 | private authService: AuthService, | 41 | private authService: AuthService, |
42 | private modalService: NgbModal, | 42 | private modalService: NgbModal, |
@@ -70,7 +70,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
70 | } | 70 | } |
71 | 71 | ||
72 | onValidKey () { | 72 | onValidKey () { |
73 | this.onValueChanged() | 73 | this.check() |
74 | if (!this.form.valid) return | 74 | if (!this.form.valid) return |
75 | 75 | ||
76 | this.formValidated() | 76 | this.formValidated() |
@@ -115,7 +115,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
115 | err => { | 115 | err => { |
116 | this.addingComment = false | 116 | this.addingComment = false |
117 | 117 | ||
118 | this.notificationsService.error(this.i18n('Error'), err.text) | 118 | this.notifier.error(err.text) |
119 | } | 119 | } |
120 | ) | 120 | ) |
121 | } | 121 | } |
@@ -135,7 +135,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
135 | 135 | ||
136 | gotoLogin () { | 136 | gotoLogin () { |
137 | this.hideVisitorModal() | 137 | this.hideVisitorModal() |
138 | this.authService.redirectUrl = this.router.url | ||
139 | this.router.navigate([ '/login' ]) | 138 | this.router.navigate([ '/login' ]) |
140 | } | 139 | } |
141 | 140 | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.scss b/client/src/app/videos/+video-watch/comment/video-comment.component.scss index 84da5727e..731ecbf8f 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.scss | |||
@@ -41,7 +41,7 @@ | |||
41 | } | 41 | } |
42 | 42 | ||
43 | .comment-date { | 43 | .comment-date { |
44 | color: #585858; | 44 | color: $grey-foreground-color; |
45 | margin-left: 10px; | 45 | margin-left: 10px; |
46 | } | 46 | } |
47 | } | 47 | } |
@@ -69,7 +69,7 @@ | |||
69 | 69 | ||
70 | .comment-action-reply, | 70 | .comment-action-reply, |
71 | .comment-action-delete { | 71 | .comment-action-delete { |
72 | color: #585858; | 72 | color: $grey-foreground-color; |
73 | cursor: pointer; | 73 | cursor: pointer; |
74 | margin-right: 10px; | 74 | margin-right: 10px; |
75 | 75 | ||
@@ -108,4 +108,4 @@ | |||
108 | .root-comment { | 108 | .root-comment { |
109 | font-size: 14px; | 109 | font-size: 14px; |
110 | } | 110 | } |
111 | } \ No newline at end of file | 111 | } |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.ts b/client/src/app/videos/+video-watch/comment/video-comment.component.ts index 00f0460a1..aba7f9d1c 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' |
2 | import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' | ||
3 | import * as sanitizeHtml from 'sanitize-html' | ||
4 | import { UserRight } from '../../../../../../shared/models/users' | 2 | import { UserRight } from '../../../../../../shared/models/users' |
5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
6 | import { AuthService } from '../../../core/auth' | 4 | import { AuthService } from '../../../core/auth' |
7 | import { Video } from '../../../shared/video/video.model' | 5 | import { Video } from '../../../shared/video/video.model' |
8 | import { VideoComment } from './video-comment.model' | 6 | import { VideoComment } from './video-comment.model' |
7 | import { HtmlRendererService } from '@app/shared/renderer' | ||
9 | 8 | ||
10 | @Component({ | 9 | @Component({ |
11 | selector: 'my-video-comment', | 10 | selector: 'my-video-comment', |
@@ -29,7 +28,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
29 | newParentComments: VideoComment[] = [] | 28 | newParentComments: VideoComment[] = [] |
30 | 29 | ||
31 | constructor ( | 30 | constructor ( |
32 | private linkifierService: LinkifierService, | 31 | private htmlRenderer: HtmlRendererService, |
33 | private authService: AuthService | 32 | private authService: AuthService |
34 | ) {} | 33 | ) {} |
35 | 34 | ||
@@ -87,27 +86,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
87 | } | 86 | } |
88 | 87 | ||
89 | private init () { | 88 | private init () { |
90 | // Convert possible markdown to html | 89 | this.sanitizedCommentHTML = this.htmlRenderer.toSafeHtml(this.comment.text) |
91 | const html = this.linkifierService.linkify(this.comment.text) | ||
92 | |||
93 | this.sanitizedCommentHTML = sanitizeHtml(html, { | ||
94 | allowedTags: [ 'a', 'p', 'span', 'br' ], | ||
95 | allowedSchemes: [ 'http', 'https' ], | ||
96 | allowedAttributes: { | ||
97 | 'a': [ 'href', 'class', 'target' ] | ||
98 | }, | ||
99 | transformTags: { | ||
100 | a: (tagName, attribs) => { | ||
101 | return { | ||
102 | tagName, | ||
103 | attribs: Object.assign(attribs, { | ||
104 | target: '_blank', | ||
105 | rel: 'noopener noreferrer' | ||
106 | }) | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | }) | ||
111 | 90 | ||
112 | this.newParentComments = this.parentComments.concat([ this.comment ]) | 91 | this.newParentComments = this.parentComments.concat([ this.comment ]) |
113 | } | 92 | } |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.service.ts b/client/src/app/videos/+video-watch/comment/video-comment.service.ts index 921447d5b..b8e5878c5 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { catchError, map } from 'rxjs/operators' | 1 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 2 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { lineFeedToHtml } from '@app/shared/misc/utils' | 4 | import { objectLineFeedToHtml } from '@app/shared/misc/utils' |
5 | import { Observable } from 'rxjs' | 5 | import { Observable } from 'rxjs' |
6 | import { ResultList, FeedFormat } from '../../../../../../shared/models' | 6 | import { ResultList, FeedFormat } from '../../../../../../shared/models' |
7 | import { | 7 | import { |
@@ -28,7 +28,7 @@ export class VideoCommentService { | |||
28 | 28 | ||
29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { | 29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { |
30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | 30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' |
31 | const normalizedComment = lineFeedToHtml(comment, 'text') | 31 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
32 | 32 | ||
33 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) | 33 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) |
34 | .pipe( | 34 | .pipe( |
@@ -39,7 +39,7 @@ export class VideoCommentService { | |||
39 | 39 | ||
40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { | 40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { |
41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId | 41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId |
42 | const normalizedComment = lineFeedToHtml(comment, 'text') | 42 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
43 | 43 | ||
44 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) | 44 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) |
45 | .pipe( | 45 | .pipe( |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.ts b/client/src/app/videos/+video-watch/comment/video-comments.component.ts index 8850eccd8..2616820d2 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ElementRef } from '@angular/core' | 1 | import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core' |
2 | import { ActivatedRoute } from '@angular/router' | 2 | import { ActivatedRoute } from '@angular/router' |
3 | import { ConfirmService } from '@app/core' | 3 | import { ConfirmService, Notifier } from '@app/core' |
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { Subscription } from 'rxjs' | 4 | import { Subscription } from 'rxjs' |
6 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
7 | import { AuthService } from '../../../core/auth' | 6 | import { AuthService } from '../../../core/auth' |
8 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' | 7 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' |
9 | import { User } from '../../../shared/users' | 8 | import { User } from '../../../shared/users' |
10 | import { VideoSortField } from '../../../shared/video/sort-field.type' | 9 | import { VideoSortField } from '../../../shared/video/sort-field.type' |
11 | import { VideoDetails } from '../../../shared/video/video-details.model' | 10 | import { VideoDetails } from '../../../shared/video/video-details.model' |
@@ -42,7 +41,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
42 | 41 | ||
43 | constructor ( | 42 | constructor ( |
44 | private authService: AuthService, | 43 | private authService: AuthService, |
45 | private notificationsService: NotificationsService, | 44 | private notifier: Notifier, |
46 | private confirmService: ConfirmService, | 45 | private confirmService: ConfirmService, |
47 | private videoCommentService: VideoCommentService, | 46 | private videoCommentService: VideoCommentService, |
48 | private activatedRoute: ActivatedRoute, | 47 | private activatedRoute: ActivatedRoute, |
@@ -84,15 +83,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
84 | this.highlightedThread = new VideoComment(res.comment) | 83 | this.highlightedThread = new VideoComment(res.comment) |
85 | 84 | ||
86 | // Scroll to the highlighted thread | 85 | // Scroll to the highlighted thread |
87 | setTimeout(() => { | 86 | setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0) |
88 | // -60 because of the fixed header | ||
89 | const scrollY = this.commentHighlightBlock.nativeElement.offsetTop - 60 | ||
90 | window.scroll(0, scrollY) | ||
91 | }, 500) | ||
92 | } | 87 | } |
93 | }, | 88 | }, |
94 | 89 | ||
95 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 90 | err => this.notifier.error(err.message) |
96 | ) | 91 | ) |
97 | } | 92 | } |
98 | 93 | ||
@@ -104,7 +99,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
104 | this.componentPagination.totalItems = res.totalComments | 99 | this.componentPagination.totalItems = res.totalComments |
105 | }, | 100 | }, |
106 | 101 | ||
107 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 102 | err => this.notifier.error(err.message) |
108 | ) | 103 | ) |
109 | } | 104 | } |
110 | 105 | ||
@@ -155,7 +150,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
155 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined | 150 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined |
156 | }, | 151 | }, |
157 | 152 | ||
158 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 153 | err => this.notifier.error(err.message) |
159 | ) | 154 | ) |
160 | } | 155 | } |
161 | 156 | ||
@@ -166,22 +161,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
166 | onNearOfBottom () { | 161 | onNearOfBottom () { |
167 | this.componentPagination.currentPage++ | 162 | this.componentPagination.currentPage++ |
168 | 163 | ||
169 | if (this.hasMoreComments()) { | 164 | if (hasMoreItems(this.componentPagination)) { |
170 | this.loadMoreComments() | 165 | this.loadMoreComments() |
171 | } | 166 | } |
172 | } | 167 | } |
173 | 168 | ||
174 | private hasMoreComments () { | ||
175 | // No results | ||
176 | if (this.componentPagination.totalItems === 0) return false | ||
177 | |||
178 | // Not loaded yet | ||
179 | if (!this.componentPagination.totalItems) return true | ||
180 | |||
181 | const maxPage = this.componentPagination.totalItems / this.componentPagination.itemsPerPage | ||
182 | return maxPage > this.componentPagination.currentPage | ||
183 | } | ||
184 | |||
185 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { | 169 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { |
186 | for (const commentChild of parentComment.children) { | 170 | for (const commentChild of parentComment.children) { |
187 | if (commentChild.comment.id === commentToDelete.id) { | 171 | if (commentChild.comment.id === commentToDelete.id) { |
diff --git a/client/src/app/videos/+video-watch/modal/video-blacklist.component.html b/client/src/app/videos/+video-watch/modal/video-blacklist.component.html index c436501b4..1a87bdcd4 100644 --- a/client/src/app/videos/+video-watch/modal/video-blacklist.component.html +++ b/client/src/app/videos/+video-watch/modal/video-blacklist.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Blacklist video</h4> | 3 | <h4 i18n class="modal-title">Blacklist video</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
@@ -15,6 +15,13 @@ | |||
15 | </div> | 15 | </div> |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <div class="form-group" *ngIf="video.isLocal"> | ||
19 | <my-peertube-checkbox | ||
20 | inputName="unfederate" formControlName="unfederate" | ||
21 | i18n-labelText labelText="Unfederate the video (ask for its deletion from the remote instances)" | ||
22 | ></my-peertube-checkbox> | ||
23 | </div> | ||
24 | |||
18 | <div class="form-group inputs"> | 25 | <div class="form-group inputs"> |
19 | <span i18n class="action-button action-button-cancel" (click)="hide()"> | 26 | <span i18n class="action-button action-button-cancel" (click)="hide()"> |
20 | Cancel | 27 | Cancel |
diff --git a/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts b/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts index 2c123ebed..50a7cadd1 100644 --- a/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier, RedirectService } from '@app/core' |
3 | import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index' | 3 | import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index' |
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 4 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { RedirectService } from '@app/core' | ||
10 | 9 | ||
11 | @Component({ | 10 | @Component({ |
12 | selector: 'my-video-blacklist', | 11 | selector: 'my-video-blacklist', |
@@ -27,7 +26,7 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
27 | private modalService: NgbModal, | 26 | private modalService: NgbModal, |
28 | private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, | 27 | private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, |
29 | private videoBlacklistService: VideoBlacklistService, | 28 | private videoBlacklistService: VideoBlacklistService, |
30 | private notificationsService: NotificationsService, | 29 | private notifier: Notifier, |
31 | private redirectService: RedirectService, | 30 | private redirectService: RedirectService, |
32 | private i18n: I18n | 31 | private i18n: I18n |
33 | ) { | 32 | ) { |
@@ -35,9 +34,12 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
35 | } | 34 | } |
36 | 35 | ||
37 | ngOnInit () { | 36 | ngOnInit () { |
37 | const defaultValues = { unfederate: 'true' } | ||
38 | |||
38 | this.buildForm({ | 39 | this.buildForm({ |
39 | reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON | 40 | reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON, |
40 | }) | 41 | unfederate: null |
42 | }, defaultValues) | ||
41 | } | 43 | } |
42 | 44 | ||
43 | show () { | 45 | show () { |
@@ -51,16 +53,17 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
51 | 53 | ||
52 | blacklist () { | 54 | blacklist () { |
53 | const reason = this.form.value[ 'reason' ] || undefined | 55 | const reason = this.form.value[ 'reason' ] || undefined |
56 | const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined | ||
54 | 57 | ||
55 | this.videoBlacklistService.blacklistVideo(this.video.id, reason) | 58 | this.videoBlacklistService.blacklistVideo(this.video.id, reason, unfederate) |
56 | .subscribe( | 59 | .subscribe( |
57 | () => { | 60 | () => { |
58 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video blacklisted.')) | 61 | this.notifier.success(this.i18n('Video blacklisted.')) |
59 | this.hide() | 62 | this.hide() |
60 | this.redirectService.redirectToHomepage() | 63 | this.redirectService.redirectToHomepage() |
61 | }, | 64 | }, |
62 | 65 | ||
63 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 66 | err => this.notifier.error(err.message) |
64 | ) | 67 | ) |
65 | } | 68 | } |
66 | } | 69 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.html b/client/src/app/videos/+video-watch/modal/video-download.component.html index f46f92a17..2bb5d6d37 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.html +++ b/client/src/app/videos/+video-watch/modal/video-download.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Download video</h4> | 3 | <h4 i18n class="modal-title">Download video</h4> |
4 | <span class="close" aria-hidden="true" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.ts b/client/src/app/videos/+video-watch/modal/video-download.component.ts index b1b2c0623..834385771 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-download.component.ts | |||
@@ -2,7 +2,7 @@ import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core' | |||
2 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-video-download', | 8 | selector: 'my-video-download', |
@@ -18,7 +18,7 @@ export class VideoDownloadComponent implements OnInit { | |||
18 | resolutionId: number | string = -1 | 18 | resolutionId: number | string = -1 |
19 | 19 | ||
20 | constructor ( | 20 | constructor ( |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private modalService: NgbModal, | 22 | private modalService: NgbModal, |
23 | private i18n: I18n | 23 | private i18n: I18n |
24 | ) { } | 24 | ) { } |
@@ -63,6 +63,6 @@ export class VideoDownloadComponent implements OnInit { | |||
63 | } | 63 | } |
64 | 64 | ||
65 | activateCopiedMessage () { | 65 | activateCopiedMessage () { |
66 | this.notificationsService.success(this.i18n('Success'), this.i18n('Copied')) | 66 | this.notifier.success(this.i18n('Copied')) |
67 | } | 67 | } |
68 | } | 68 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.html b/client/src/app/videos/+video-watch/modal/video-report.component.html index 8d9a49276..b9434da26 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.html +++ b/client/src/app/videos/+video-watch/modal/video-report.component.html | |||
@@ -1,11 +1,16 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Report video</h4> | 3 | <h4 i18n class="modal-title">Report video</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
8 | 8 | ||
9 | <div i18n class="information"> | ||
10 | Your report will be sent to moderators of {{ currentHost }}. | ||
11 | <ng-container *ngIf="isRemoteVideo()"> It will be forwarded to origin instance {{ originHost }} too.</ng-container> | ||
12 | </div> | ||
13 | |||
9 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> | 14 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> |
10 | <div class="form-group"> | 15 | <div class="form-group"> |
11 | <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"> | 16 | <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.scss b/client/src/app/videos/+video-watch/modal/video-report.component.scss index afcdb9a16..4713660a2 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.scss +++ b/client/src/app/videos/+video-watch/modal/video-report.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | .information { | ||
5 | margin-bottom: 20px; | ||
6 | } | ||
7 | |||
4 | textarea { | 8 | textarea { |
5 | @include peertube-textarea(100%, 100px); | 9 | @include peertube-textarea(100%, 100px); |
6 | } | 10 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.ts b/client/src/app/videos/+video-watch/modal/video-report.component.ts index 297afb19f..911f3b447 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-report.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, VideoAbuseService } from '../../../shared/index' | 3 | import { FormReactive, VideoAbuseService } from '../../../shared/index' |
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 4 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -27,12 +27,24 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
27 | private modalService: NgbModal, | 27 | private modalService: NgbModal, |
28 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 28 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, |
29 | private videoAbuseService: VideoAbuseService, | 29 | private videoAbuseService: VideoAbuseService, |
30 | private notificationsService: NotificationsService, | 30 | private notifier: Notifier, |
31 | private i18n: I18n | 31 | private i18n: I18n |
32 | ) { | 32 | ) { |
33 | super() | 33 | super() |
34 | } | 34 | } |
35 | 35 | ||
36 | get currentHost () { | ||
37 | return window.location.host | ||
38 | } | ||
39 | |||
40 | get originHost () { | ||
41 | if (this.isRemoteVideo()) { | ||
42 | return this.video.account.host | ||
43 | } | ||
44 | |||
45 | return '' | ||
46 | } | ||
47 | |||
36 | ngOnInit () { | 48 | ngOnInit () { |
37 | this.buildForm({ | 49 | this.buildForm({ |
38 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON | 50 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON |
@@ -54,11 +66,15 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
54 | this.videoAbuseService.reportVideo(this.video.id, reason) | 66 | this.videoAbuseService.reportVideo(this.video.id, reason) |
55 | .subscribe( | 67 | .subscribe( |
56 | () => { | 68 | () => { |
57 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video reported.')) | 69 | this.notifier.success(this.i18n('Video reported.')) |
58 | this.hide() | 70 | this.hide() |
59 | }, | 71 | }, |
60 | 72 | ||
61 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 73 | err => this.notifier.error(err.message) |
62 | ) | 74 | ) |
63 | } | 75 | } |
76 | |||
77 | isRemoteVideo () { | ||
78 | return !this.video.isLocal | ||
79 | } | ||
64 | } | 80 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.html b/client/src/app/videos/+video-watch/modal/video-share.component.html index 301f67f2d..9f3c37fe8 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.html +++ b/client/src/app/videos/+video-watch/modal/video-share.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Share</h4> | 3 | <h4 i18n class="modal-title">Share</h4> |
4 | <span class="close" aria-hidden="true" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.ts b/client/src/app/videos/+video-watch/modal/video-share.component.ts index 17e2b31e1..c6205e355 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-share.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { VideoDetails } from '../../../shared/video/video-details.model' | 3 | import { VideoDetails } from '../../../shared/video/video-details.model' |
4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' | 4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -23,7 +23,7 @@ export class VideoShareComponent { | |||
23 | 23 | ||
24 | constructor ( | 24 | constructor ( |
25 | private modalService: NgbModal, | 25 | private modalService: NgbModal, |
26 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
27 | private i18n: I18n | 27 | private i18n: I18n |
28 | ) { } | 28 | ) { } |
29 | 29 | ||
@@ -49,7 +49,7 @@ export class VideoShareComponent { | |||
49 | } | 49 | } |
50 | 50 | ||
51 | activateCopiedMessage () { | 51 | activateCopiedMessage () { |
52 | this.notificationsService.success(this.i18n('Success'), this.i18n('Copied')) | 52 | this.notifier.success(this.i18n('Copied')) |
53 | } | 53 | } |
54 | 54 | ||
55 | getStartCheckboxLabel () { | 55 | getStartCheckboxLabel () { |
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.html b/client/src/app/videos/+video-watch/modal/video-support.component.html index 00c304709..2a05224a8 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.html +++ b/client/src/app/videos/+video-watch/modal/video-support.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Support</h4> | 3 | <h4 i18n class="modal-title">Support</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> | 7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> |
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.ts b/client/src/app/videos/+video-watch/modal/video-support.component.ts index 154002120..deb8fbc67 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-support.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | 1 | import { Component, Input, ViewChild } from '@angular/core' |
2 | import { MarkdownService } from '@app/videos/shared' | ||
3 | |||
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { MarkdownService } from '@app/shared/renderer' | ||
6 | 5 | ||
7 | @Component({ | 6 | @Component({ |
8 | selector: 'my-video-support', | 7 | selector: 'my-video-support', |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 770785d02..709eb91a8 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -53,55 +53,57 @@ | |||
53 | <div | 53 | <div |
54 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" | 54 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" |
55 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" | 55 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" |
56 | i18n-title title="Like this video" | ||
56 | > | 57 | > |
57 | <span class="icon icon-like" i18n-title title="Like this video" ></span> | 58 | <my-global-icon iconName="like"></my-global-icon> |
58 | </div> | 59 | </div> |
59 | 60 | ||
60 | <div | 61 | <div |
61 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" | 62 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" |
62 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" | 63 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" |
64 | i18n-title title="Dislike this video" | ||
63 | > | 65 | > |
64 | <span class="icon icon-dislike" i18n-title title="Dislike this video"></span> | 66 | <my-global-icon iconName="dislike"></my-global-icon> |
65 | </div> | 67 | </div> |
66 | 68 | ||
67 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> | 69 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> |
68 | <span class="icon icon-support"></span> | 70 | <my-global-icon iconName="heart"></my-global-icon> |
69 | <span class="icon-text" i18n>Support</span> | 71 | <span class="icon-text" i18n>Support</span> |
70 | </div> | 72 | </div> |
71 | 73 | ||
72 | <div (click)="showShareModal()" class="action-button action-button-share" role="button"> | 74 | <div (click)="showShareModal()" class="action-button action-button-share" role="button"> |
73 | <span class="icon icon-share"></span> | 75 | <my-global-icon iconName="share"></my-global-icon> |
74 | <span class="icon-text" i18n>Share</span> | 76 | <span class="icon-text" i18n>Share</span> |
75 | </div> | 77 | </div> |
76 | 78 | ||
77 | <div class="action-more" ngbDropdown placement="top" role="button"> | 79 | <div class="action-more" ngbDropdown placement="top" role="button"> |
78 | <div class="action-button" ngbDropdownToggle role="button"> | 80 | <div class="action-button" ngbDropdownToggle role="button"> |
79 | <span class="icon icon-more"></span> | 81 | <my-global-icon class="more-icon" iconName="more"></my-global-icon> |
80 | </div> | 82 | </div> |
81 | 83 | ||
82 | <div ngbDropdownMenu> | 84 | <div ngbDropdownMenu> |
83 | <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> | 85 | <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> |
84 | <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container> | 86 | <my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container> |
85 | </a> | 87 | </a> |
86 | 88 | ||
87 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> | 89 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> |
88 | <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container> | 90 | <my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container> |
89 | </a> | 91 | </a> |
90 | 92 | ||
91 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> | 93 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> |
92 | <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container> | 94 | <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container> |
93 | </a> | 95 | </a> |
94 | 96 | ||
95 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> | 97 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> |
96 | <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> | 98 | <my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container> |
97 | </a> | 99 | </a> |
98 | 100 | ||
99 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> | 101 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> |
100 | <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container> | 102 | <my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container> |
101 | </a> | 103 | </a> |
102 | 104 | ||
103 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> | 105 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> |
104 | <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container> | 106 | <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container> |
105 | </a> | 107 | </a> |
106 | </div> | 108 | </div> |
107 | </div> | 109 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index 2586a2204..b03ed197d 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -183,6 +183,8 @@ $other-videos-width: 260px; | |||
183 | .action-button { | 183 | .action-button { |
184 | @include peertube-button; | 184 | @include peertube-button; |
185 | @include grey-button; | 185 | @include grey-button; |
186 | @include button-with-icon(21px, 0, -1px); | ||
187 | @include apply-svg-color($grey-foreground-color); | ||
186 | 188 | ||
187 | font-size: 15px; | 189 | font-size: 15px; |
188 | font-weight: $font-semibold; | 190 | font-weight: $font-semibold; |
@@ -194,53 +196,25 @@ $other-videos-width: 260px; | |||
194 | display: none; | 196 | display: none; |
195 | } | 197 | } |
196 | 198 | ||
197 | .icon { | ||
198 | @include icon(21px); | ||
199 | |||
200 | position: relative; | ||
201 | top: -2px; | ||
202 | |||
203 | &.icon-like { | ||
204 | background-image: url('../../../assets/images/video/like-grey.svg'); | ||
205 | } | ||
206 | |||
207 | &.icon-dislike { | ||
208 | background-image: url('../../../assets/images/video/dislike-grey.svg'); | ||
209 | } | ||
210 | |||
211 | &.icon-support { | ||
212 | background-image: url('../../../assets/images/video/heart.svg'); | ||
213 | } | ||
214 | |||
215 | &.icon-share { | ||
216 | background-image: url('../../../assets/images/video/share.svg'); | ||
217 | } | ||
218 | |||
219 | &.icon-more { | ||
220 | background-image: url('../../../assets/images/video/more.svg'); | ||
221 | top: -1px; | ||
222 | } | ||
223 | } | ||
224 | |||
225 | .icon-text { | ||
226 | margin-left: 3px; | ||
227 | } | ||
228 | |||
229 | &.action-button-like.activated { | 199 | &.action-button-like.activated { |
230 | background-color: $green; | 200 | background-color: $green; |
231 | 201 | ||
232 | .icon-like { | 202 | my-global-icon { |
233 | background-image: url('../../../assets/images/video/like-white.svg'); | 203 | @include apply-svg-color(#fff); |
234 | } | 204 | } |
235 | } | 205 | } |
236 | 206 | ||
237 | &.action-button-dislike.activated { | 207 | &.action-button-dislike.activated { |
238 | background-color: $red; | 208 | background-color: $red; |
239 | 209 | ||
240 | .icon-dislike { | 210 | my-global-icon { |
241 | background-image: url('../../../assets/images/video/dislike-white.svg'); | 211 | @include apply-svg-color(#fff); |
242 | } | 212 | } |
243 | } | 213 | } |
214 | |||
215 | .icon-text { | ||
216 | margin-left: 3px; | ||
217 | } | ||
244 | } | 218 | } |
245 | 219 | ||
246 | .action-more { | 220 | .action-more { |
@@ -249,36 +223,12 @@ $other-videos-width: 260px; | |||
249 | .dropdown-menu .dropdown-item { | 223 | .dropdown-menu .dropdown-item { |
250 | padding: 6px 24px; | 224 | padding: 6px 24px; |
251 | 225 | ||
252 | .icon { | 226 | my-global-icon { |
253 | @include icon(24px); | 227 | width: 24px; |
254 | 228 | ||
255 | margin-right: 10px; | 229 | margin-right: 10px; |
256 | position: relative; | 230 | position: relative; |
257 | top: -1px; | 231 | top: -2px; |
258 | |||
259 | &.icon-download { | ||
260 | background-image: url('../../../assets/images/video/download-black.svg'); | ||
261 | } | ||
262 | |||
263 | &.icon-edit { | ||
264 | background-image: url('../../../assets/images/global/edit-black.svg'); | ||
265 | } | ||
266 | |||
267 | &.icon-alert { | ||
268 | background-image: url('../../../assets/images/video/alert.svg'); | ||
269 | } | ||
270 | |||
271 | &.icon-blacklist { | ||
272 | background-image: url('../../../assets/images/video/blacklist.svg'); | ||
273 | } | ||
274 | |||
275 | &.icon-unblacklist { | ||
276 | background-image: url('../../../assets/images/global/undo.svg'); | ||
277 | } | ||
278 | |||
279 | &.icon-delete { | ||
280 | background-image: url('../../../assets/images/global/delete-black.svg'); | ||
281 | } | ||
282 | } | 232 | } |
283 | } | 233 | } |
284 | } | 234 | } |
@@ -320,7 +270,7 @@ $other-videos-width: 260px; | |||
320 | .video-info-description-more { | 270 | .video-info-description-more { |
321 | cursor: pointer; | 271 | cursor: pointer; |
322 | font-weight: $font-semibold; | 272 | font-weight: $font-semibold; |
323 | color: #585858; | 273 | color: $grey-foreground-color; |
324 | font-size: 14px; | 274 | font-size: 14px; |
325 | 275 | ||
326 | .glyphicon { | 276 | .glyphicon { |
@@ -339,7 +289,7 @@ $other-videos-width: 260px; | |||
339 | min-width: 91px; | 289 | min-width: 91px; |
340 | padding-right: 5px; | 290 | padding-right: 5px; |
341 | display: inline-block; | 291 | display: inline-block; |
342 | color: #585858; | 292 | color: $grey-foreground-color; |
343 | font-weight: $font-bold; | 293 | font-weight: $font-bold; |
344 | } | 294 | } |
345 | 295 | ||
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 09ee96bdc..ee504bc58 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -5,7 +5,7 @@ import { RedirectService } from '@app/core/routing/redirect.service' | |||
5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
7 | import { MetaService } from '@ngx-meta/core' | 7 | import { MetaService } from '@ngx-meta/core' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { Notifier, ServerService } from '@app/core' |
9 | import { forkJoin, Subscription } from 'rxjs' | 9 | import { forkJoin, Subscription } from 'rxjs' |
10 | // FIXME: something weird with our path definition in tsconfig and typings | 10 | // FIXME: something weird with our path definition in tsconfig and typings |
11 | // @ts-ignore | 11 | // @ts-ignore |
@@ -13,24 +13,23 @@ import videojs from 'video.js' | |||
13 | import 'videojs-hotkeys' | 13 | import 'videojs-hotkeys' |
14 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 14 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
15 | import * as WebTorrent from 'webtorrent' | 15 | import * as WebTorrent from 'webtorrent' |
16 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' | 16 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
17 | import '../../../assets/player/peertube-videojs-plugin' | 17 | import '../../../assets/player/peertube-videojs-plugin' |
18 | import { AuthService, ConfirmService } from '../../core' | 18 | import { AuthService, ConfirmService } from '../../core' |
19 | import { RestExtractor, VideoBlacklistService } from '../../shared' | 19 | import { RestExtractor, VideoBlacklistService } from '../../shared' |
20 | import { VideoDetails } from '../../shared/video/video-details.model' | 20 | import { VideoDetails } from '../../shared/video/video-details.model' |
21 | import { VideoService } from '../../shared/video/video.service' | 21 | import { VideoService } from '../../shared/video/video.service' |
22 | import { MarkdownService } from '../shared' | ||
23 | import { VideoDownloadComponent } from './modal/video-download.component' | 22 | import { VideoDownloadComponent } from './modal/video-download.component' |
24 | import { VideoReportComponent } from './modal/video-report.component' | 23 | import { VideoReportComponent } from './modal/video-report.component' |
25 | import { VideoShareComponent } from './modal/video-share.component' | 24 | import { VideoShareComponent } from './modal/video-share.component' |
26 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' | 25 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' |
27 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' | 26 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' |
28 | import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player' | 27 | import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player' |
29 | import { ServerService } from '@app/core' | ||
30 | import { I18n } from '@ngx-translate/i18n-polyfill' | 28 | import { I18n } from '@ngx-translate/i18n-polyfill' |
31 | import { environment } from '../../../environments/environment' | 29 | import { environment } from '../../../environments/environment' |
32 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 30 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
33 | import { VideoCaptionService } from '@app/shared/video-caption' | 31 | import { VideoCaptionService } from '@app/shared/video-caption' |
32 | import { MarkdownService } from '@app/shared/renderer' | ||
34 | 33 | ||
35 | @Component({ | 34 | @Component({ |
36 | selector: 'my-video-watch', | 35 | selector: 'my-video-watch', |
@@ -77,7 +76,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
77 | private authService: AuthService, | 76 | private authService: AuthService, |
78 | private serverService: ServerService, | 77 | private serverService: ServerService, |
79 | private restExtractor: RestExtractor, | 78 | private restExtractor: RestExtractor, |
80 | private notificationsService: NotificationsService, | 79 | private notifier: Notifier, |
81 | private markdownService: MarkdownService, | 80 | private markdownService: MarkdownService, |
82 | private zone: NgZone, | 81 | private zone: NgZone, |
83 | private redirectService: RedirectService, | 82 | private redirectService: RedirectService, |
@@ -118,7 +117,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
118 | ) | 117 | ) |
119 | .subscribe(([ video, captionsResult ]) => { | 118 | .subscribe(([ video, captionsResult ]) => { |
120 | const startTime = this.route.snapshot.queryParams.start | 119 | const startTime = this.route.snapshot.queryParams.start |
121 | this.onVideoFetched(video, captionsResult.data, startTime) | 120 | const subtitle = this.route.snapshot.queryParams.subtitle |
121 | |||
122 | this.onVideoFetched(video, captionsResult.data, { startTime, subtitle }) | ||
122 | .catch(err => this.handleError(err)) | 123 | .catch(err => this.handleError(err)) |
123 | }) | 124 | }) |
124 | }) | 125 | }) |
@@ -203,7 +204,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
203 | 204 | ||
204 | error => { | 205 | error => { |
205 | this.descriptionLoading = false | 206 | this.descriptionLoading = false |
206 | this.notificationsService.error(this.i18n('Error'), error.message) | 207 | this.notifier.error(error.message) |
207 | } | 208 | } |
208 | ) | 209 | ) |
209 | } | 210 | } |
@@ -245,16 +246,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
245 | 246 | ||
246 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( | 247 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( |
247 | () => { | 248 | () => { |
248 | this.notificationsService.success( | 249 | this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })) |
249 | this.i18n('Success'), | ||
250 | this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name }) | ||
251 | ) | ||
252 | 250 | ||
253 | this.video.blacklisted = false | 251 | this.video.blacklisted = false |
254 | this.video.blacklistedReason = null | 252 | this.video.blacklistedReason = null |
255 | }, | 253 | }, |
256 | 254 | ||
257 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 255 | err => this.notifier.error(err.message) |
258 | ) | 256 | ) |
259 | } | 257 | } |
260 | 258 | ||
@@ -292,17 +290,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
292 | 290 | ||
293 | this.videoService.removeVideo(this.video.id) | 291 | this.videoService.removeVideo(this.video.id) |
294 | .subscribe( | 292 | .subscribe( |
295 | status => { | 293 | () => { |
296 | this.notificationsService.success( | 294 | this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })) |
297 | this.i18n('Success'), | ||
298 | this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) | ||
299 | ) | ||
300 | 295 | ||
301 | // Go back to the video-list. | 296 | // Go back to the video-list. |
302 | this.redirectService.redirectToHomepage() | 297 | this.redirectService.redirectToHomepage() |
303 | }, | 298 | }, |
304 | 299 | ||
305 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 300 | error => this.notifier.error(error.message) |
306 | ) | 301 | ) |
307 | } | 302 | } |
308 | 303 | ||
@@ -352,7 +347,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
352 | return | 347 | return |
353 | } | 348 | } |
354 | 349 | ||
355 | this.notificationsService.error(this.i18n('Error'), errorMessage) | 350 | this.notifier.error(errorMessage) |
356 | } | 351 | } |
357 | 352 | ||
358 | private checkUserRating () { | 353 | private checkUserRating () { |
@@ -367,11 +362,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
367 | } | 362 | } |
368 | }, | 363 | }, |
369 | 364 | ||
370 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 365 | err => this.notifier.error(err.message) |
371 | ) | 366 | ) |
372 | } | 367 | } |
373 | 368 | ||
374 | private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) { | 369 | private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], urlOptions: { startTime: number, subtitle: string }) { |
375 | this.video = video | 370 | this.video = video |
376 | 371 | ||
377 | // Re init attributes | 372 | // Re init attributes |
@@ -379,8 +374,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
379 | this.completeDescriptionShown = false | 374 | this.completeDescriptionShown = false |
380 | this.remoteServerDown = false | 375 | this.remoteServerDown = false |
381 | 376 | ||
382 | let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0) | 377 | let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0) |
383 | // Don't start the video if we are at the end | 378 | // If we are at the end of the video, reset the timer |
384 | if (this.video.duration - startTime <= 1) startTime = 0 | 379 | if (this.video.duration - startTime <= 1) startTime = 0 |
385 | 380 | ||
386 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { | 381 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { |
@@ -419,10 +414,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
419 | peertubeLink: false, | 414 | peertubeLink: false, |
420 | poster: this.video.previewUrl, | 415 | poster: this.video.previewUrl, |
421 | startTime, | 416 | startTime, |
417 | subtitle: urlOptions.subtitle, | ||
422 | theaterMode: true, | 418 | theaterMode: true, |
423 | language: this.localeId, | 419 | language: this.localeId, |
424 | 420 | ||
425 | userWatching: this.user ? { | 421 | userWatching: this.user && this.user.videosHistoryEnabled === true ? { |
426 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | 422 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), |
427 | authorizationHeader: this.authService.getRequestHeaderValue() | 423 | authorizationHeader: this.authService.getRequestHeaderValue() |
428 | } : undefined | 424 | } : undefined |
@@ -472,7 +468,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
472 | this.userRating = nextRating | 468 | this.userRating = nextRating |
473 | }, | 469 | }, |
474 | 470 | ||
475 | (err: { message: string }) => this.notificationsService.error(this.i18n('Error'), err.message) | 471 | (err: { message: string }) => this.notifier.error(err.message) |
476 | ) | 472 | ) |
477 | } | 473 | } |
478 | 474 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts index 54a12c126..2f448db78 100644 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ b/client/src/app/videos/+video-watch/video-watch.module.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' | ||
3 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 2 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
4 | import { ClipboardModule } from 'ngx-clipboard' | 3 | import { ClipboardModule } from 'ngx-clipboard' |
5 | import { SharedModule } from '../../shared' | 4 | import { SharedModule } from '../../shared' |
6 | import { MarkdownService } from '../shared' | ||
7 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' | 5 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' |
8 | import { VideoCommentComponent } from './comment/video-comment.component' | 6 | import { VideoCommentComponent } from './comment/video-comment.component' |
9 | import { VideoCommentService } from './comment/video-comment.service' | 7 | import { VideoCommentService } from './comment/video-comment.service' |
@@ -46,8 +44,6 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio | |||
46 | ], | 44 | ], |
47 | 45 | ||
48 | providers: [ | 46 | providers: [ |
49 | MarkdownService, | ||
50 | LinkifierService, | ||
51 | VideoCommentService | 47 | VideoCommentService |
52 | ] | 48 | ] |
53 | }) | 49 | }) |
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts deleted file mode 100644 index 7a66944b9..000000000 --- a/client/src/app/videos/shared/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './markdown.service' | ||
diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts index 9d000cf2e..c0be4b885 100644 --- a/client/src/app/videos/video-list/video-local.component.ts +++ b/client/src/app/videos/video-list/video-local.component.ts | |||
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { immutableAssign } from '@app/shared/misc/utils' | 3 | import { immutableAssign } from '@app/shared/misc/utils' |
4 | import { Location } from '@angular/common' | 4 | import { Location } from '@angular/common' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
@@ -11,6 +10,7 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ | |||
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { ScreenService } from '@app/shared/misc/screen.service' | 11 | import { ScreenService } from '@app/shared/misc/screen.service' |
13 | import { UserRight } from '../../../../../shared/models/users' | 12 | import { UserRight } from '../../../../../shared/models/users' |
13 | import { Notifier } from '@app/core' | ||
14 | 14 | ||
15 | @Component({ | 15 | @Component({ |
16 | selector: 'my-videos-local', | 16 | selector: 'my-videos-local', |
@@ -26,7 +26,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On | |||
26 | constructor ( | 26 | constructor ( |
27 | protected router: Router, | 27 | protected router: Router, |
28 | protected route: ActivatedRoute, | 28 | protected route: ActivatedRoute, |
29 | protected notificationsService: NotificationsService, | 29 | protected notifier: Notifier, |
30 | protected authService: AuthService, | 30 | protected authService: AuthService, |
31 | protected location: Location, | 31 | protected location: Location, |
32 | protected i18n: I18n, | 32 | protected i18n: I18n, |
diff --git a/client/src/app/videos/video-list/video-overview.component.ts b/client/src/app/videos/video-list/video-overview.component.ts index 2c6054721..7ff52b259 100644 --- a/client/src/app/videos/video-list/video-overview.component.ts +++ b/client/src/app/videos/video-list/video-overview.component.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { AuthService } from '@app/core' | 2 | import { AuthService, Notifier } from '@app/core' |
3 | import { NotificationsService } from 'angular2-notifications' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' | 4 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' |
6 | import { OverviewService } from '@app/shared/overview' | 5 | import { OverviewService } from '@app/shared/overview' |
@@ -21,7 +20,7 @@ export class VideoOverviewComponent implements OnInit { | |||
21 | 20 | ||
22 | constructor ( | 21 | constructor ( |
23 | private i18n: I18n, | 22 | private i18n: I18n, |
24 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
25 | private authService: AuthService, | 24 | private authService: AuthService, |
26 | private overviewService: OverviewService | 25 | private overviewService: OverviewService |
27 | ) { } | 26 | ) { } |
@@ -43,10 +42,7 @@ export class VideoOverviewComponent implements OnInit { | |||
43 | ) this.notResults = true | 42 | ) this.notResults = true |
44 | }, | 43 | }, |
45 | 44 | ||
46 | err => { | 45 | err => this.notifier.error(err.message) |
47 | console.log(err) | ||
48 | this.notificationsService.error('Error', err.text) | ||
49 | } | ||
50 | ) | 46 | ) |
51 | } | 47 | } |
52 | 48 | ||
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts index ac1fcfff3..f99c8abb6 100644 --- a/client/src/app/videos/video-list/video-recently-added.component.ts +++ b/client/src/app/videos/video-list/video-recently-added.component.ts | |||
@@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Location } from '@angular/common' | 3 | import { Location } from '@angular/common' |
4 | import { immutableAssign } from '@app/shared/misc/utils' | 4 | import { immutableAssign } from '@app/shared/misc/utils' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
9 | import { VideoService } from '../../shared/video/video.service' | 8 | import { VideoService } from '../../shared/video/video.service' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
11 | import { Notifier } from '@app/core' | ||
12 | 12 | ||
13 | @Component({ | 13 | @Component({ |
14 | selector: 'my-videos-recently-added', | 14 | selector: 'my-videos-recently-added', |
@@ -24,7 +24,7 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On | |||
24 | protected router: Router, | 24 | protected router: Router, |
25 | protected route: ActivatedRoute, | 25 | protected route: ActivatedRoute, |
26 | protected location: Location, | 26 | protected location: Location, |
27 | protected notificationsService: NotificationsService, | 27 | protected notifier: Notifier, |
28 | protected authService: AuthService, | 28 | protected authService: AuthService, |
29 | protected i18n: I18n, | 29 | protected i18n: I18n, |
30 | protected screenService: ScreenService, | 30 | protected screenService: ScreenService, |
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts index 8f3d3842b..6fd74e67a 100644 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ b/client/src/app/videos/video-list/video-trending.component.ts | |||
@@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Location } from '@angular/common' | 3 | import { Location } from '@angular/common' |
4 | import { immutableAssign } from '@app/shared/misc/utils' | 4 | import { immutableAssign } from '@app/shared/misc/utils' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
9 | import { VideoService } from '../../shared/video/video.service' | 8 | import { VideoService } from '../../shared/video/video.service' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
11 | import { Notifier, ServerService } from '@app/core' | ||
12 | 12 | ||
13 | @Component({ | 13 | @Component({ |
14 | selector: 'my-videos-trending', | 14 | selector: 'my-videos-trending', |
@@ -23,22 +23,37 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, | |||
23 | constructor ( | 23 | constructor ( |
24 | protected router: Router, | 24 | protected router: Router, |
25 | protected route: ActivatedRoute, | 25 | protected route: ActivatedRoute, |
26 | protected notificationsService: NotificationsService, | 26 | protected notifier: Notifier, |
27 | protected authService: AuthService, | 27 | protected authService: AuthService, |
28 | protected location: Location, | 28 | protected location: Location, |
29 | protected screenService: ScreenService, | 29 | protected screenService: ScreenService, |
30 | private serverService: ServerService, | ||
30 | protected i18n: I18n, | 31 | protected i18n: I18n, |
31 | private videoService: VideoService | 32 | private videoService: VideoService |
32 | ) { | 33 | ) { |
33 | super() | 34 | super() |
34 | |||
35 | this.titlePage = i18n('Trending') | ||
36 | } | 35 | } |
37 | 36 | ||
38 | ngOnInit () { | 37 | ngOnInit () { |
39 | super.ngOnInit() | 38 | super.ngOnInit() |
40 | 39 | ||
41 | this.generateSyndicationList() | 40 | this.generateSyndicationList() |
41 | |||
42 | this.serverService.configLoaded.subscribe( | ||
43 | () => { | ||
44 | const trendingDays = this.serverService.getConfig().trending.videos.intervalDays | ||
45 | |||
46 | if (trendingDays === 1) { | ||
47 | this.titlePage = this.i18n('Trending for the last 24 hours') | ||
48 | this.titleTooltip = this.i18n('Trending videos are those totalizing the greatest number of views during the last 24 hours.') | ||
49 | } else { | ||
50 | this.titlePage = this.i18n('Trending for the last {{days}} days', { days: trendingDays }) | ||
51 | this.titleTooltip = this.i18n( | ||
52 | 'Trending videos are those totalizing the greatest number of views during the last {{days}} days.', | ||
53 | { days: trendingDays } | ||
54 | ) | ||
55 | } | ||
56 | }) | ||
42 | } | 57 | } |
43 | 58 | ||
44 | ngOnDestroy () { | 59 | ngOnDestroy () { |
diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts index 6e8959c54..bee828e12 100644 --- a/client/src/app/videos/video-list/video-user-subscriptions.component.ts +++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts | |||
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' | |||
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { immutableAssign } from '@app/shared/misc/utils' | 3 | import { immutableAssign } from '@app/shared/misc/utils' |
4 | import { Location } from '@angular/common' | 4 | import { Location } from '@angular/common' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | 5 | import { AuthService } from '../../core/auth' |
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | 6 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' |
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | 7 | import { VideoSortField } from '../../shared/video/sort-field.type' |
@@ -10,6 +9,7 @@ import { VideoService } from '../../shared/video/video.service' | |||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { ScreenService } from '@app/shared/misc/screen.service' | 10 | import { ScreenService } from '@app/shared/misc/screen.service' |
12 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | 11 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' |
12 | import { Notifier } from '@app/core' | ||
13 | 13 | ||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-videos-user-subscriptions', | 15 | selector: 'my-videos-user-subscriptions', |
@@ -25,7 +25,7 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
25 | constructor ( | 25 | constructor ( |
26 | protected router: Router, | 26 | protected router: Router, |
27 | protected route: ActivatedRoute, | 27 | protected route: ActivatedRoute, |
28 | protected notificationsService: NotificationsService, | 28 | protected notifier: Notifier, |
29 | protected authService: AuthService, | 29 | protected authService: AuthService, |
30 | protected location: Location, | 30 | protected location: Location, |
31 | protected i18n: I18n, | 31 | protected i18n: I18n, |