diff options
Diffstat (limited to 'client/src/app')
33 files changed, 370 insertions, 254 deletions
diff --git a/client/src/app/+about/about-follows/about-follows.component.html b/client/src/app/+about/about-follows/about-follows.component.html index 2cf890acf..e9139b503 100644 --- a/client/src/app/+about/about-follows/about-follows.component.html +++ b/client/src/app/+about/about-follows/about-follows.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="row"> | 1 | <div class="row"> |
2 | <h1 class="sr-only" i18n>Follows</h1> | 2 | <h1 class="sr-only" i18n>Follows</h1> |
3 | <div class="col-xl-6 col-md-12"> | 3 | <div class="col-xl-6 col-md-12"> |
4 | <h2 i18n class="subtitle">Followers instances ({{ followersPagination.totalItems }})</h2> | 4 | <h2 i18n class="subtitle">Follower instances ({{ followersPagination.totalItems }})</h2> |
5 | 5 | ||
6 | <div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have instances followers.</div> | 6 | <div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have instances followers.</div> |
7 | 7 | ||
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 d8794d602..1f372090e 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -83,7 +83,7 @@ | |||
83 | fragment="business-model" | 83 | fragment="business-model" |
84 | #anchorLink | 84 | #anchorLink |
85 | (click)="onClickCopyLink(anchorLink)"> | 85 | (click)="onClickCopyLink(anchorLink)"> |
86 | <h3 i18n class="section-title">How we will pay for this instance</h3> | 86 | <h3 i18n class="section-title">How we will pay for keeping our instance running</h3> |
87 | </a> | 87 | </a> |
88 | 88 | ||
89 | <div [innerHTML]="html.businessModel"></div> | 89 | <div [innerHTML]="html.businessModel"></div> |
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 5bd7b0824..1903bb36f 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -12,7 +12,7 @@ | |||
12 | <button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()" | 12 | <button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()" |
13 | class="btn btn-outline-secondary btn-sm copy-button" | 13 | class="btn btn-outline-secondary btn-sm copy-button" |
14 | > | 14 | > |
15 | <span class="glyphicon glyphicon-copy"></span> | 15 | <span class="glyphicon glyphicon-duplicate"></span> |
16 | </button> | 16 | </button> |
17 | </div> | 17 | </div> |
18 | <span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span> | 18 | <span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span> |
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html index 1b5fe45c6..8edf03a89 100644 --- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html +++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html | |||
@@ -3,7 +3,7 @@ | |||
3 | </div> | 3 | </div> |
4 | 4 | ||
5 | <div class="search-bar"> | 5 | <div class="search-bar"> |
6 | <input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..."/> | 6 | <input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..." autofocus /> |
7 | </div> | 7 | </div> |
8 | 8 | ||
9 | <div class="alert alert-info" i18n *ngIf="pluginInstalled"> | 9 | <div class="alert alert-info" i18n *ngIf="pluginInstalled"> |
@@ -20,8 +20,8 @@ | |||
20 | <my-global-icon iconName="search"></my-global-icon> | 20 | <my-global-icon iconName="search"></my-global-icon> |
21 | 21 | ||
22 | <ng-container i18n> | 22 | <ng-container i18n> |
23 | {{ pagination.totalItems }} {pagination.totalItems, plural, =1 {result} other {results}} for "{{ search }}" | 23 | {{ pagination.totalItems }} {pagination.totalItems, plural, =1 {result} other {results}} for {{ search }}" |
24 | </ng-container> | 24 | </ng-container> |
25 | </ng-container> | 25 | </ng-container> |
26 | </div> | 26 | </div> |
27 | 27 | ||
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index 3171e5b0f..0167066a0 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html | |||
@@ -21,7 +21,7 @@ | |||
21 | <label i18n for="username">User</label> | 21 | <label i18n for="username">User</label> |
22 | <input | 22 | <input |
23 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" | 23 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" |
24 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput | 24 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" autofocus |
25 | > | 25 | > |
26 | </div> | 26 | </div> |
27 | 27 | ||
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index af747b7fa..d8ad49081 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts | |||
@@ -3,9 +3,9 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angula | |||
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' | ||
7 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' | 6 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' |
8 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
8 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' | ||
9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
10 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | 10 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' |
11 | 11 | ||
@@ -16,7 +16,6 @@ import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | |||
16 | }) | 16 | }) |
17 | 17 | ||
18 | export class LoginComponent extends FormReactive implements OnInit, AfterViewInit { | 18 | export class LoginComponent extends FormReactive implements OnInit, AfterViewInit { |
19 | @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef | ||
20 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef | 19 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef |
21 | 20 | ||
22 | accordion: NgbAccordion | 21 | accordion: NgbAccordion |
@@ -91,10 +90,6 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
91 | } | 90 | } |
92 | 91 | ||
93 | ngAfterViewInit () { | 92 | ngAfterViewInit () { |
94 | if (this.usernameInput) { | ||
95 | this.usernameInput.nativeElement.focus() | ||
96 | } | ||
97 | |||
98 | this.hooks.runAction('action:login.init', 'login') | 93 | this.hooks.runAction('action:login.init', 'login') |
99 | } | 94 | } |
100 | 95 | ||
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 index ad7497f45..c7e173038 100644 --- 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 | |||
@@ -42,7 +42,9 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
42 | newInstanceFollower: $localize`Your instance has a new follower`, | 42 | newInstanceFollower: $localize`Your instance has a new follower`, |
43 | autoInstanceFollowing: $localize`Your instance automatically followed another instance`, | 43 | autoInstanceFollowing: $localize`Your instance automatically followed another instance`, |
44 | abuseNewMessage: $localize`An abuse report received a new message`, | 44 | abuseNewMessage: $localize`An abuse report received a new message`, |
45 | abuseStateChange: $localize`One of your abuse reports has been accepted or rejected by moderators` | 45 | abuseStateChange: $localize`One of your abuse reports has been accepted or rejected by moderators`, |
46 | newPeerTubeVersion: $localize`A new PeerTube version is available`, | ||
47 | newPluginVersion: $localize`One of your plugin/theme has a new available version` | ||
46 | } | 48 | } |
47 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] | 49 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] |
48 | 50 | ||
@@ -51,7 +53,9 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
51 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, | 53 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, |
52 | newUserRegistration: UserRight.MANAGE_USERS, | 54 | newUserRegistration: UserRight.MANAGE_USERS, |
53 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, | 55 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, |
54 | autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION | 56 | autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION, |
57 | newPeerTubeVersion: UserRight.MANAGE_DEBUG, | ||
58 | newPluginVersion: UserRight.MANAGE_DEBUG | ||
55 | } | 59 | } |
56 | } | 60 | } |
57 | 61 | ||
diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html index 84be4fb14..74c6839e1 100644 --- a/client/src/app/+search/search.component.html +++ b/client/src/app/+search/search.component.html | |||
@@ -2,14 +2,12 @@ | |||
2 | <div class="results-header"> | 2 | <div class="results-header"> |
3 | <div class="first-line"> | 3 | <div class="first-line"> |
4 | <div class="results-counter" *ngIf="pagination.totalItems"> | 4 | <div class="results-counter" *ngIf="pagination.totalItems"> |
5 | <span i18n>{{ pagination.totalItems | myNumberFormatter }} {pagination.totalItems, plural, =1 {result} other {results}} </span> | 5 | <span class="mr-1" i18n>{{ pagination.totalItems | myNumberFormatter }} {pagination.totalItems, plural, =1 {result} other {results}}</span> |
6 | 6 | ||
7 | <span i18n *ngIf="advancedSearch.searchTarget === 'local'">on this instance</span> | 7 | <span class="mr-1" i18n *ngIf="advancedSearch.searchTarget === 'local'">on this instance</span> |
8 | <span i18n *ngIf="advancedSearch.searchTarget === 'search-index'">on the vidiverse</span> | 8 | <span class="mr-1" i18n *ngIf="advancedSearch.searchTarget === 'search-index'">on the vidiverse</span> |
9 | 9 | ||
10 | <span *ngIf="currentSearch" i18n> | 10 | <span *ngIf="currentSearch" i18n>for <span class="search-value">{{ currentSearch }}</span></span> |
11 | for <span class="search-value">{{ currentSearch }}</span> | ||
12 | </span> | ||
13 | </div> | 11 | </div> |
14 | 12 | ||
15 | <div | 13 | <div |
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html index 03770ceec..594935afd 100644 --- a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html +++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <div i18n class="title-page title-page-single"> | 2 | <div i18n class="title-page title-page-single"> |
3 | Created {{ pagination.totalItems }} playlists | 3 | Created {pagination.totalItems, plural, =1 {1 playlist} other {{{ pagination.totalItems }} playlists}} |
4 | </div> | 4 | </div> |
5 | 5 | ||
6 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div> | 6 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div> |
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html index 4b0d12b6e..b3ea19768 100644 --- a/client/src/app/+video-channels/video-channels.component.html +++ b/client/src/app/+video-channels/video-channels.component.html | |||
@@ -12,7 +12,7 @@ | |||
12 | <button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()" | 12 | <button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()" |
13 | class="btn btn-outline-secondary btn-sm copy-button" | 13 | class="btn btn-outline-secondary btn-sm copy-button" |
14 | > | 14 | > |
15 | <span class="glyphicon glyphicon-copy"></span> | 15 | <span class="glyphicon glyphicon-duplicate"></span> |
16 | </button> | 16 | </button> |
17 | </div> | 17 | </div> |
18 | </div> | 18 | </div> |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts index 8780ca567..8e035b6bb 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | 1 | ||
2 | import { forkJoin } from 'rxjs' | 2 | import { forkJoin } from 'rxjs' |
3 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 3 | import { AfterViewChecked, AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core' |
4 | import { Router } from '@angular/router' | 4 | import { Router } from '@angular/router' |
5 | import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core' | 5 | import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core' |
6 | import { scrollToTop } from '@app/helpers' | 6 | import { scrollToTop } from '@app/helpers' |
7 | import { FormValidatorService } from '@app/shared/shared-forms' | 7 | import { FormValidatorService } from '@app/shared/shared-forms' |
8 | import { VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' | 8 | import { VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' |
@@ -19,7 +19,7 @@ import { VideoSend } from './video-send' | |||
19 | './video-send.scss' | 19 | './video-send.scss' |
20 | ] | 20 | ] |
21 | }) | 21 | }) |
22 | export class VideoGoLiveComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 22 | export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterViewInit, CanComponentDeactivate { |
23 | @Output() firstStepDone = new EventEmitter<string>() | 23 | @Output() firstStepDone = new EventEmitter<string>() |
24 | @Output() firstStepError = new EventEmitter<void>() | 24 | @Output() firstStepError = new EventEmitter<void>() |
25 | 25 | ||
@@ -41,7 +41,8 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, CanCompon | |||
41 | protected videoService: VideoService, | 41 | protected videoService: VideoService, |
42 | protected videoCaptionService: VideoCaptionService, | 42 | protected videoCaptionService: VideoCaptionService, |
43 | private liveVideoService: LiveVideoService, | 43 | private liveVideoService: LiveVideoService, |
44 | private router: Router | 44 | private router: Router, |
45 | private hooks: HooksService | ||
45 | ) { | 46 | ) { |
46 | super() | 47 | super() |
47 | } | 48 | } |
@@ -50,6 +51,10 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, CanCompon | |||
50 | super.ngOnInit() | 51 | super.ngOnInit() |
51 | } | 52 | } |
52 | 53 | ||
54 | ngAfterViewInit () { | ||
55 | this.hooks.runAction('action:go-live.init', 'video-edit') | ||
56 | } | ||
57 | |||
53 | canDeactivate () { | 58 | canDeactivate () { |
54 | return { canDeactivate: true } | 59 | return { canDeactivate: true } |
55 | } | 60 | } |
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 01087e525..3aae24732 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,6 +1,6 @@ | |||
1 | import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { AfterViewInit, Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core' | 3 | import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core' |
4 | import { scrollToTop } from '@app/helpers' | 4 | import { scrollToTop } from '@app/helpers' |
5 | import { FormValidatorService } from '@app/shared/shared-forms' | 5 | import { FormValidatorService } from '@app/shared/shared-forms' |
6 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' | 6 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' |
@@ -18,7 +18,7 @@ import { VideoSend } from './video-send' | |||
18 | './video-send.scss' | 18 | './video-send.scss' |
19 | ] | 19 | ] |
20 | }) | 20 | }) |
21 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 21 | export class VideoImportTorrentComponent extends VideoSend implements OnInit, AfterViewInit, CanComponentDeactivate { |
22 | @Output() firstStepDone = new EventEmitter<string>() | 22 | @Output() firstStepDone = new EventEmitter<string>() |
23 | @Output() firstStepError = new EventEmitter<void>() | 23 | @Output() firstStepError = new EventEmitter<void>() |
24 | @ViewChild('torrentfileInput') torrentfileInput: ElementRef<HTMLInputElement> | 24 | @ViewChild('torrentfileInput') torrentfileInput: ElementRef<HTMLInputElement> |
@@ -43,7 +43,8 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
43 | protected videoService: VideoService, | 43 | protected videoService: VideoService, |
44 | protected videoCaptionService: VideoCaptionService, | 44 | protected videoCaptionService: VideoCaptionService, |
45 | private router: Router, | 45 | private router: Router, |
46 | private videoImportService: VideoImportService | 46 | private videoImportService: VideoImportService, |
47 | private hooks: HooksService | ||
47 | ) { | 48 | ) { |
48 | super() | 49 | super() |
49 | } | 50 | } |
@@ -52,6 +53,10 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
52 | super.ngOnInit() | 53 | super.ngOnInit() |
53 | } | 54 | } |
54 | 55 | ||
56 | ngAfterViewInit () { | ||
57 | this.hooks.runAction('action:video-torrent-import.init', 'video-edit') | ||
58 | } | ||
59 | |||
55 | canDeactivate () { | 60 | canDeactivate () { |
56 | return { canDeactivate: true } | 61 | return { canDeactivate: true } |
57 | } | 62 | } |
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 c447c179d..7a9fe369f 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,7 +1,7 @@ | |||
1 | import { map, switchMap } from 'rxjs/operators' | 1 | import { map, switchMap } from 'rxjs/operators' |
2 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 2 | import { AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core' |
3 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
4 | import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core' | 4 | import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core' |
5 | import { getAbsoluteAPIUrl, scrollToTop } from '@app/helpers' | 5 | import { getAbsoluteAPIUrl, scrollToTop } from '@app/helpers' |
6 | import { FormValidatorService } from '@app/shared/shared-forms' | 6 | import { FormValidatorService } from '@app/shared/shared-forms' |
7 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' | 7 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' |
@@ -18,7 +18,7 @@ import { VideoSend } from './video-send' | |||
18 | './video-send.scss' | 18 | './video-send.scss' |
19 | ] | 19 | ] |
20 | }) | 20 | }) |
21 | export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate { | 21 | export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterViewInit, CanComponentDeactivate { |
22 | @Output() firstStepDone = new EventEmitter<string>() | 22 | @Output() firstStepDone = new EventEmitter<string>() |
23 | @Output() firstStepError = new EventEmitter<void>() | 23 | @Output() firstStepError = new EventEmitter<void>() |
24 | 24 | ||
@@ -42,8 +42,9 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
42 | protected videoService: VideoService, | 42 | protected videoService: VideoService, |
43 | protected videoCaptionService: VideoCaptionService, | 43 | protected videoCaptionService: VideoCaptionService, |
44 | private router: Router, | 44 | private router: Router, |
45 | private videoImportService: VideoImportService | 45 | private videoImportService: VideoImportService, |
46 | ) { | 46 | private hooks: HooksService |
47 | ) { | ||
47 | super() | 48 | super() |
48 | } | 49 | } |
49 | 50 | ||
@@ -51,6 +52,10 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
51 | super.ngOnInit() | 52 | super.ngOnInit() |
52 | } | 53 | } |
53 | 54 | ||
55 | ngAfterViewInit () { | ||
56 | this.hooks.runAction('action:video-url-import.init', 'video-edit') | ||
57 | } | ||
58 | |||
54 | canDeactivate () { | 59 | canDeactivate () { |
55 | return { canDeactivate: true } | 60 | return { canDeactivate: true } |
56 | } | 61 | } |
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 ca21b61cd..effb37077 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 | |||
@@ -1,15 +1,15 @@ | |||
1 | import { Subscription } from 'rxjs' | 1 | import { Subscription } from 'rxjs' |
2 | import { HttpErrorResponse, HttpEventType, HttpResponse } from '@angular/common/http' | 2 | import { HttpErrorResponse, HttpEventType, HttpResponse } from '@angular/common/http' |
3 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' | 3 | import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' |
4 | import { Router } from '@angular/router' | 4 | import { Router } from '@angular/router' |
5 | import { AuthService, CanComponentDeactivate, Notifier, ServerService, UserService } from '@app/core' | 5 | import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService, UserService } from '@app/core' |
6 | import { scrollToTop, uploadErrorHandler } from '@app/helpers' | 6 | import { scrollToTop, uploadErrorHandler } from '@app/helpers' |
7 | import { FormValidatorService } from '@app/shared/shared-forms' | 7 | import { FormValidatorService } from '@app/shared/shared-forms' |
8 | import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' | 8 | import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' |
9 | import { LoadingBarService } from '@ngx-loading-bar/core' | 9 | import { LoadingBarService } from '@ngx-loading-bar/core' |
10 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
10 | import { VideoPrivacy } from '@shared/models' | 11 | import { VideoPrivacy } from '@shared/models' |
11 | import { VideoSend } from './video-send' | 12 | import { VideoSend } from './video-send' |
12 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
13 | 13 | ||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-video-upload', | 15 | selector: 'my-video-upload', |
@@ -20,7 +20,7 @@ import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | |||
20 | './video-send.scss' | 20 | './video-send.scss' |
21 | ] | 21 | ] |
22 | }) | 22 | }) |
23 | export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate { | 23 | export class VideoUploadComponent extends VideoSend implements OnInit, AfterViewInit, OnDestroy, CanComponentDeactivate { |
24 | @Output() firstStepDone = new EventEmitter<string>() | 24 | @Output() firstStepDone = new EventEmitter<string>() |
25 | @Output() firstStepError = new EventEmitter<void>() | 25 | @Output() firstStepError = new EventEmitter<void>() |
26 | @ViewChild('videofileInput') videofileInput: ElementRef<HTMLInputElement> | 26 | @ViewChild('videofileInput') videofileInput: ElementRef<HTMLInputElement> |
@@ -60,7 +60,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
60 | protected videoService: VideoService, | 60 | protected videoService: VideoService, |
61 | protected videoCaptionService: VideoCaptionService, | 61 | protected videoCaptionService: VideoCaptionService, |
62 | private userService: UserService, | 62 | private userService: UserService, |
63 | private router: Router | 63 | private router: Router, |
64 | private hooks: HooksService | ||
64 | ) { | 65 | ) { |
65 | super() | 66 | super() |
66 | } | 67 | } |
@@ -79,6 +80,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
79 | }) | 80 | }) |
80 | } | 81 | } |
81 | 82 | ||
83 | ngAfterViewInit () { | ||
84 | this.hooks.runAction('action:video-upload.init', 'video-edit') | ||
85 | } | ||
86 | |||
82 | ngOnDestroy () { | 87 | ngOnDestroy () { |
83 | if (this.videoUploadObservable) this.videoUploadObservable.unsubscribe() | 88 | if (this.videoUploadObservable) this.videoUploadObservable.unsubscribe() |
84 | } | 89 | } |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.html b/client/src/app/+videos/+video-watch/comment/video-comments.component.html index 4a6426d30..9e6fde2e0 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.html | |||
@@ -1,12 +1,7 @@ | |||
1 | <div> | 1 | <div> |
2 | <div class="title-block"> | 2 | <div class="title-block"> |
3 | <h2 class="title-page title-page-single"> | 3 | <h2 class="title-page title-page-single"> |
4 | <ng-container *ngIf="totalNotDeletedComments > 0; then hasComments; else noComments"></ng-container> | 4 | {totalNotDeletedComments, plural, =0 {Comments} =1 {1 Comment} other {{{totalNotDeletedComments}} Comments}} |
5 | <ng-template #hasComments> | ||
6 | <ng-container i18n *ngIf="totalNotDeletedComments === 1; else manyComments">1 Comment</ng-container> | ||
7 | <ng-template i18n #manyComments>{{ totalNotDeletedComments }} Comments</ng-template> | ||
8 | </ng-template> | ||
9 | <ng-template i18n #noComments>Comments</ng-template> | ||
10 | </h2> | 5 | </h2> |
11 | 6 | ||
12 | <my-feed [syndicationItems]="syndicationItems"></my-feed> | 7 | <my-feed [syndicationItems]="syndicationItems"></my-feed> |
@@ -79,15 +74,17 @@ | |||
79 | <span class="glyphicon glyphicon-menu-down"></span> | 74 | <span class="glyphicon glyphicon-menu-down"></span> |
80 | 75 | ||
81 | <ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container> | 76 | <ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container> |
77 | |||
82 | <ng-template #hasAuthorComments> | 78 | <ng-template #hasAuthorComments> |
83 | <ng-container *ngIf="comment.totalReplies !== comment.totalRepliesFromVideoAuthor; else onlyAuthorComments" i18n> | 79 | <ng-container *ngIf="comment.totalReplies !== comment.totalRepliesFromVideoAuthor; else onlyAuthorComments" i18n> |
84 | View {{ comment.totalReplies }} replies from {{ video?.account?.displayName || 'the author' }} and others | 80 | View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}} from {{ video?.account?.displayName || 'the author' }} and others |
85 | </ng-container> | 81 | </ng-container> |
86 | <ng-template i18n #onlyAuthorComments> | 82 | <ng-template i18n #onlyAuthorComments> |
87 | View {{ comment.totalReplies }} replies from {{ video?.account?.displayName || 'the author' }} | 83 | View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}} from {{ video?.account?.displayName || 'the author' }} |
88 | </ng-template> | 84 | </ng-template> |
89 | </ng-template> | 85 | </ng-template> |
90 | <ng-template i18n #noAuthorComments>View {{ comment.totalReplies }} replies</ng-template> | 86 | |
87 | <ng-template i18n #noAuthorComments>View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}}</ng-template> | ||
91 | 88 | ||
92 | <my-small-loader class="comment-thread-loading ml-1" [loading]="threadLoading[comment.id]"></my-small-loader> | 89 | <my-small-loader class="comment-thread-loading ml-1" [loading]="threadLoading[comment.id]"></my-small-loader> |
93 | </div> | 90 | </div> |
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 d36dd9e34..210236b61 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 | |||
@@ -5,7 +5,6 @@ import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifie | |||
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { Syndication, VideoDetails } from '@app/shared/shared-main' | 6 | import { Syndication, VideoDetails } from '@app/shared/shared-main' |
7 | import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment' | 7 | import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment' |
8 | import { ThisReceiver } from '@angular/compiler' | ||
9 | 8 | ||
10 | @Component({ | 9 | @Component({ |
11 | selector: 'my-video-comments', | 10 | selector: 'my-video-comments', |
diff --git a/client/src/app/core/notification/peertube-socket.service.ts b/client/src/app/core/notification/peertube-socket.service.ts index bc3f7b893..eab1c63f2 100644 --- a/client/src/app/core/notification/peertube-socket.service.ts +++ b/client/src/app/core/notification/peertube-socket.service.ts | |||
@@ -58,12 +58,11 @@ export class PeerTubeSocket { | |||
58 | this.notificationSocket = this.io(environment.apiUrl + '/user-notifications', { | 58 | this.notificationSocket = this.io(environment.apiUrl + '/user-notifications', { |
59 | query: { accessToken: this.auth.getAccessToken() } | 59 | query: { accessToken: this.auth.getAccessToken() } |
60 | }) | 60 | }) |
61 | |||
62 | this.notificationSocket.on('new-notification', (n: UserNotificationServer) => { | ||
63 | this.ngZone.run(() => this.dispatchNotificationEvent('new', n)) | ||
64 | }) | ||
65 | }) | 61 | }) |
66 | 62 | ||
63 | this.notificationSocket.on('new-notification', (n: UserNotificationServer) => { | ||
64 | this.ngZone.run(() => this.dispatchNotificationEvent('new', n)) | ||
65 | }) | ||
67 | } | 66 | } |
68 | 67 | ||
69 | private async initLiveVideosSocket () { | 68 | private async initLiveVideosSocket () { |
diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts index ec47aa48c..ddde198d2 100644 --- a/client/src/app/core/plugins/hooks.service.ts +++ b/client/src/app/core/plugins/hooks.service.ts | |||
@@ -3,13 +3,29 @@ import { mergeMap, switchMap } from 'rxjs/operators' | |||
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { PluginService } from '@app/core/plugins/plugin.service' | 4 | import { PluginService } from '@app/core/plugins/plugin.service' |
5 | import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models' | 5 | import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models' |
6 | import { AuthService, AuthStatus } from '../auth' | ||
6 | 7 | ||
7 | type RawFunction<U, T> = (params: U) => T | 8 | type RawFunction<U, T> = (params: U) => T |
8 | type ObservableFunction<U, T> = RawFunction<U, Observable<T>> | 9 | type ObservableFunction<U, T> = RawFunction<U, Observable<T>> |
9 | 10 | ||
10 | @Injectable() | 11 | @Injectable() |
11 | export class HooksService { | 12 | export class HooksService { |
12 | constructor (private pluginService: PluginService) { } | 13 | constructor ( |
14 | private authService: AuthService, | ||
15 | private pluginService: PluginService | ||
16 | ) { | ||
17 | // Run auth hooks | ||
18 | this.authService.userInformationLoaded | ||
19 | .subscribe(() => this.runAction('action:auth-user.information-loaded', 'common', { user: this.authService.getUser() })) | ||
20 | |||
21 | this.authService.loginChangedSource.subscribe(obj => { | ||
22 | if (obj === AuthStatus.LoggedIn) { | ||
23 | this.runAction('action:auth-user.logged-in', 'common') | ||
24 | } else if (obj === AuthStatus.LoggedOut) { | ||
25 | this.runAction('action:auth-user.logged-out', 'common') | ||
26 | } | ||
27 | }) | ||
28 | } | ||
13 | 29 | ||
14 | wrapObsFun | 30 | wrapObsFun |
15 | <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName> | 31 | <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName> |
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index b755fda2c..54dba5e17 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -235,6 +235,12 @@ export class PluginService implements ClientHook { | |||
235 | .toPromise() | 235 | .toPromise() |
236 | }, | 236 | }, |
237 | 237 | ||
238 | getServerConfig: () => { | ||
239 | return this.server.getConfig() | ||
240 | .pipe(catchError(res => this.restExtractor.handleError(res))) | ||
241 | .toPromise() | ||
242 | }, | ||
243 | |||
238 | isLoggedIn: () => { | 244 | isLoggedIn: () => { |
239 | return this.authService.isLoggedIn() | 245 | return this.authService.isLoggedIn() |
240 | }, | 246 | }, |
diff --git a/client/src/app/header/search-typeahead.component.html b/client/src/app/header/search-typeahead.component.html index 03e86b8e6..f84086b4a 100644 --- a/client/src/app/header/search-typeahead.component.html +++ b/client/src/app/header/search-typeahead.component.html | |||
@@ -34,7 +34,8 @@ | |||
34 | 34 | ||
35 | <!-- search instructions, when search input is empty --> | 35 | <!-- search instructions, when search input is empty --> |
36 | <div *ngIf="areInstructionsDisplayed()" id="typeahead-instructions" class="overflow-hidden"> | 36 | <div *ngIf="areInstructionsDisplayed()" id="typeahead-instructions" class="overflow-hidden"> |
37 | <div class="d-flex justify-content-between"> | 37 | <span class="text-muted" i18n>Your query will be matched against video names or descriptions, channel names.</span> |
38 | <div class="d-flex justify-content-between mt-3"> | ||
38 | <label class="small-title" i18n>ADVANCED SEARCH</label> | 39 | <label class="small-title" i18n>ADVANCED SEARCH</label> |
39 | <div class="advanced-search-status c-help"> | 40 | <div class="advanced-search-status c-help"> |
40 | <span [ngClass]="canSearchAnyURI ? 'text-success' : 'text-muted'" i18n-title title="Determines whether you can resolve any distant content, or if this instance only allows doing so for instances it follows."> | 41 | <span [ngClass]="canSearchAnyURI ? 'text-success' : 'text-muted'" i18n-title title="Determines whether you can resolve any distant content, or if this instance only allows doing so for instances it follows."> |
@@ -55,7 +56,6 @@ | |||
55 | <em>UUID</em> <span class="text-muted" i18n>will list the matching video</span> | 56 | <em>UUID</em> <span class="text-muted" i18n>will list the matching video</span> |
56 | </li> | 57 | </li> |
57 | </ul> | 58 | </ul> |
58 | <span class="text-muted" i18n>Any other input will return matching video or channel names.</span> | ||
59 | </div> | 59 | </div> |
60 | </div> | 60 | </div> |
61 | 61 | ||
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 2ea66e57d..aa247d268 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | $menu-link-icon-size: 22px; | 4 | $menu-link-icon-size: 22px; |
5 | $menu-link-icon-margin-right: 18px; | 5 | $menu-link-icon-margin-right: 18px; |
6 | $footer-links-base-opacity: .8; | ||
6 | 7 | ||
7 | @mixin menu-link { | 8 | @mixin menu-link { |
8 | display: flex; | 9 | display: flex; |
@@ -91,168 +92,168 @@ menu { | |||
91 | align-items: center; | 92 | align-items: center; |
92 | justify-content: left; | 93 | justify-content: left; |
93 | 94 | ||
94 | .logged-in-more { | 95 | my-notification { |
95 | $main-radius: 25px; | 96 | margin-left: auto; |
97 | margin-right: 15px; | ||
98 | } | ||
99 | } | ||
100 | } | ||
96 | 101 | ||
97 | flex: 1; | 102 | .logged-in-more { |
98 | margin-left: 13px; | 103 | $main-radius: 25px; |
99 | border-radius: $main-radius; | ||
100 | transition: all .1s ease-in-out; | ||
101 | cursor: pointer; | ||
102 | 104 | ||
103 | *, & { | 105 | flex: 1; |
104 | line-height: 1; | 106 | margin-left: 13px; |
105 | } | 107 | border-radius: $main-radius; |
108 | transition: all .1s ease-in-out; | ||
109 | cursor: pointer; | ||
106 | 110 | ||
107 | &.show { | 111 | *, & { |
108 | background-color: rgba(255, 255, 255, 0.20); | 112 | line-height: 1; |
109 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325); | 113 | } |
110 | } | ||
111 | 114 | ||
112 | @mixin display-hints($is-mobile: false) { | 115 | &.show { |
113 | background-color: rgba(255, 255, 255, 0.15); | 116 | background-color: rgba(255, 255, 255, 0.20); |
114 | 117 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325); | |
115 | @if $is-mobile { | 118 | } |
116 | .dropdown-toggle-indicator { | 119 | |
117 | display: inherit !important; | 120 | @mixin display-hints($is-mobile: false) { |
118 | } | 121 | background-color: rgba(255, 255, 255, 0.15); |
119 | .dropdown-toggle:first-child { | ||
120 | padding-right: 30px !important; | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | 122 | ||
125 | &:hover { | 123 | @if $is-mobile { |
126 | @include display-hints; | 124 | .dropdown-toggle-indicator { |
125 | display: inherit !important; | ||
126 | } | ||
127 | .dropdown-toggle:first-child { | ||
128 | padding-right: 30px !important; | ||
127 | } | 129 | } |
130 | } | ||
131 | } | ||
128 | 132 | ||
129 | /* smartphones and touchscreens */ | 133 | &:hover { |
130 | @media (hover: none) and (pointer: coarse) { | 134 | @include display-hints; |
131 | @include display-hints($is-mobile: true); | 135 | } |
132 | 136 | ||
133 | /* fill space when on mobile */ | 137 | /* smartphones and touchscreens */ |
134 | max-width: calc(100% - 80px); | 138 | @media (hover: none) and (pointer: coarse) { |
135 | .dropdown-toggle { | 139 | @include display-hints($is-mobile: true); |
136 | max-width: 100%; | ||
137 | } | ||
138 | .logged-in-info { | ||
139 | max-width: calc(100% - 45px) !important; | ||
140 | } | ||
141 | 140 | ||
142 | } | 141 | /* fill space when on mobile */ |
142 | max-width: calc(100% - 80px); | ||
143 | .dropdown-toggle { | ||
144 | max-width: 100%; | ||
145 | } | ||
146 | .logged-in-info { | ||
147 | max-width: calc(100% - 45px) !important; | ||
148 | } | ||
143 | 149 | ||
144 | .dropdown-toggle-indicator { | 150 | } |
145 | position: relative; | ||
146 | width: 0; | ||
147 | display: none; | ||
148 | |||
149 | span { | ||
150 | position: absolute; | ||
151 | right: -35px; | ||
152 | top: -8px; | ||
153 | color: grey; | ||
154 | width: $main-radius; | ||
155 | } | ||
156 | } | ||
157 | 151 | ||
158 | .dropdown-toggle { | 152 | .dropdown-toggle-indicator { |
159 | &::after { | 153 | position: relative; |
160 | border: none; | 154 | width: 0; |
161 | } | 155 | display: none; |
162 | } | ||
163 | 156 | ||
164 | .dropdown-toggle:first-child { | 157 | span { |
165 | display: flex; | 158 | position: absolute; |
166 | align-items: center; | 159 | right: -35px; |
167 | padding: 5px 7px; | 160 | top: -8px; |
168 | border-radius: $main-radius; | 161 | color: grey; |
169 | } | 162 | width: $main-radius; |
163 | } | ||
164 | } | ||
170 | 165 | ||
171 | img { | 166 | .dropdown-toggle { |
172 | @include avatar(34px); | 167 | &::after { |
168 | border: none; | ||
169 | } | ||
170 | } | ||
173 | 171 | ||
174 | margin-right: 10px; | 172 | .dropdown-toggle:first-child { |
175 | } | 173 | display: flex; |
174 | align-items: center; | ||
175 | padding: 5px 7px; | ||
176 | border-radius: $main-radius; | ||
177 | } | ||
178 | |||
179 | img { | ||
180 | @include avatar(34px); | ||
176 | 181 | ||
177 | .logged-in-info { | 182 | margin-right: 10px; |
178 | max-width: 105px; | 183 | } |
184 | } | ||
179 | 185 | ||
180 | flex-grow: 1; | 186 | .logged-in-info { |
187 | max-width: 105px; | ||
181 | 188 | ||
182 | .logged-in-display-name, | 189 | flex-grow: 1; |
183 | .logged-in-username { | ||
184 | @include ellipsis; | ||
185 | } | ||
186 | 190 | ||
187 | .logged-in-display-name { | 191 | .logged-in-display-name, |
188 | font-size: 16px; | 192 | .logged-in-username { |
189 | font-weight: $font-semibold; | 193 | @include ellipsis; |
190 | color: pvar(--menuForegroundColor); | 194 | } |
191 | 195 | ||
192 | @include disable-default-a-behaviour; | 196 | .logged-in-display-name { |
193 | } | 197 | font-size: 16px; |
198 | font-weight: $font-semibold; | ||
199 | color: pvar(--menuForegroundColor); | ||
194 | 200 | ||
195 | .logged-in-username { | 201 | @include disable-default-a-behaviour; |
196 | font-size: 13px; | 202 | } |
197 | color: #C6C6C6; | ||
198 | margin-top: 3px; | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | 203 | ||
203 | my-notification { | 204 | .logged-in-username { |
204 | margin-left: auto; | 205 | font-size: 13px; |
205 | margin-right: 15px; | 206 | color: #C6C6C6; |
206 | } | 207 | margin-top: 3px; |
207 | } | 208 | } |
209 | } | ||
208 | 210 | ||
209 | .logged-in-menu { | 211 | .logged-in-menu { |
210 | display: flex; | 212 | display: flex; |
211 | flex-direction: column; | 213 | flex-direction: column; |
212 | align-items: flex-start; | 214 | align-items: flex-start; |
213 | border-top: 1px solid var(--greyForegroundColor); | 215 | border-top: 1px solid var(--greyForegroundColor); |
214 | line-height: $line-height-normal; | 216 | line-height: $line-height-normal; |
215 | 217 | ||
216 | a { | 218 | a { |
217 | @include menu-link; | 219 | @include menu-link; |
218 | @include disable-default-a-behaviour; | 220 | @include disable-default-a-behaviour; |
219 | 221 | ||
220 | $icon-size: 13px; | 222 | $icon-size: 13px; |
221 | $additional-margin: ($menu-link-icon-size - $icon-size) / 2; | 223 | $additional-margin: ($menu-link-icon-size - $icon-size) / 2; |
222 | 224 | ||
223 | font-size: 14px; | 225 | font-size: 14px; |
224 | width: 100%; | 226 | width: 100%; |
225 | min-height: 35px; | 227 | min-height: 35px; |
226 | 228 | ||
227 | my-global-icon { | 229 | my-global-icon { |
228 | width: $icon-size; | 230 | width: $icon-size; |
229 | height: $icon-size; | 231 | height: $icon-size; |
230 | 232 | ||
231 | // Keep aligned with other icons | 233 | // Keep aligned with other icons |
232 | margin-left: $additional-margin; | 234 | margin-left: $additional-margin; |
233 | 235 | ||
234 | &[iconName="channel"] { | 236 | &[iconName="channel"] { |
235 | margin-top: -2px; | 237 | margin-top: -2px; |
236 | } | ||
237 | } | 238 | } |
239 | } | ||
238 | 240 | ||
239 | &.active, | 241 | &.active, |
240 | &:hover, | 242 | &:hover, |
241 | &:focus-visible { | 243 | &:focus-visible { |
242 | my-global-icon { | 244 | my-global-icon { |
243 | @include apply-svg-color(var(--menuForegroundColor)); | 245 | @include apply-svg-color(var(--menuForegroundColor)); |
244 | } | ||
245 | } | 246 | } |
247 | } | ||
246 | 248 | ||
247 | &.active { | 249 | &.active { |
248 | $border-left-width: 4px; | 250 | $border-left-width: 4px; |
249 | 251 | ||
250 | font-weight: $font-semibold; | 252 | font-weight: $font-semibold; |
251 | border-left: $border-left-width solid var(--mainColor); | 253 | border-left: $border-left-width solid var(--mainColor); |
252 | 254 | ||
253 | my-global-icon { | 255 | my-global-icon { |
254 | margin-left: $additional-margin - $border-left-width; | 256 | margin-left: $additional-margin - $border-left-width; |
255 | } | ||
256 | } | 257 | } |
257 | } | 258 | } |
258 | } | 259 | } |
@@ -333,50 +334,48 @@ menu { | |||
333 | flex-direction: column; | 334 | flex-direction: column; |
334 | padding: 0 $menu-lateral-padding; | 335 | padding: 0 $menu-lateral-padding; |
335 | } | 336 | } |
337 | } | ||
336 | 338 | ||
337 | $footer-links-base-opacity: .8; | 339 | .footer-links { |
338 | 340 | &, > div { | |
339 | .footer-links { | 341 | display: flex; |
340 | &, > div { | 342 | flex-wrap: wrap; |
341 | display: flex; | 343 | } |
342 | flex-wrap: wrap; | ||
343 | } | ||
344 | 344 | ||
345 | a, span[role=button] { | 345 | a, span[role=button] { |
346 | display: inline-block; | 346 | display: inline-block; |
347 | text-decoration: none; | 347 | text-decoration: none; |
348 | color: pvar(--menuForegroundColor); | 348 | color: pvar(--menuForegroundColor); |
349 | opacity: $footer-links-base-opacity; | 349 | opacity: $footer-links-base-opacity; |
350 | white-space: nowrap; | ||
351 | font-size: 90%; | ||
352 | font-weight: 500; | ||
353 | line-height: 1.4rem; | ||
354 | margin-right: 8px; | ||
355 | |||
356 | &.inline-global-icon { | ||
357 | display: inline-flex; | ||
358 | align-items: center; | ||
350 | white-space: nowrap; | 359 | white-space: nowrap; |
351 | font-size: 90%; | 360 | height: 1.4rem; |
352 | font-weight: 500; | 361 | |
353 | line-height: 1.4rem; | 362 | my-global-icon { |
354 | margin-right: 8px; | 363 | @include apply-svg-color(pvar(--menuForegroundColor)); |
355 | 364 | ||
356 | &.inline-global-icon { | 365 | display: flex; |
357 | display: inline-flex; | 366 | width: auto; |
358 | align-items: center; | 367 | height: 90%; |
359 | white-space: nowrap; | 368 | margin-right: .2rem; |
360 | height: 1.4rem; | ||
361 | |||
362 | my-global-icon { | ||
363 | @include apply-svg-color(pvar(--menuForegroundColor)); | ||
364 | |||
365 | display: flex; | ||
366 | width: auto; | ||
367 | height: 90%; | ||
368 | margin-right: .2rem; | ||
369 | } | ||
370 | } | 369 | } |
371 | } | 370 | } |
372 | } | 371 | } |
372 | } | ||
373 | 373 | ||
374 | .footer-copyleft small a { | 374 | .footer-copyleft small a { |
375 | @include disable-default-a-behaviour; | 375 | @include disable-default-a-behaviour; |
376 | 376 | ||
377 | color: pvar(--menuForegroundColor); | 377 | color: pvar(--menuForegroundColor); |
378 | opacity: $footer-links-base-opacity - .2; | 378 | opacity: $footer-links-base-opacity - .2; |
379 | } | ||
380 | } | 379 | } |
381 | 380 | ||
382 | .dropdown { | 381 | .dropdown { |
diff --git a/client/src/app/modal/instance-config-warning-modal.component.html b/client/src/app/modal/instance-config-warning-modal.component.html index 5a8adf726..498adfeff 100644 --- a/client/src/app/modal/instance-config-warning-modal.component.html +++ b/client/src/app/modal/instance-config-warning-modal.component.html | |||
@@ -15,7 +15,7 @@ | |||
15 | 15 | ||
16 | <li i18n *ngIf="!about.instance.administrator">Who you are</li> | 16 | <li i18n *ngIf="!about.instance.administrator">Who you are</li> |
17 | <li i18n *ngIf="!about.instance.maintenanceLifetime">How long you plan to maintain your instance</li> | 17 | <li i18n *ngIf="!about.instance.maintenanceLifetime">How long you plan to maintain your instance</li> |
18 | <li i18n *ngIf="!about.instance.businessModel">How you plan to pay your instance</li> | 18 | <li i18n *ngIf="!about.instance.businessModel">How you plan to pay for keeping your instance running</li> |
19 | 19 | ||
20 | <li i18n *ngIf="!about.instance.moderationInformation">How you will moderate your instance</li> | 20 | <li i18n *ngIf="!about.instance.moderationInformation">How you will moderate your instance</li> |
21 | <li i18n *ngIf="!about.instance.terms">Instance terms</li> | 21 | <li i18n *ngIf="!about.instance.terms">Instance terms</li> |
diff --git a/client/src/app/shared/shared-forms/input-toggle-hidden.component.html b/client/src/app/shared/shared-forms/input-toggle-hidden.component.html index e7441e4c1..9f252f299 100644 --- a/client/src/app/shared/shared-forms/input-toggle-hidden.component.html +++ b/client/src/app/shared/shared-forms/input-toggle-hidden.component.html | |||
@@ -12,9 +12,10 @@ | |||
12 | 12 | ||
13 | <button | 13 | <button |
14 | *ngIf="withCopy" [cdkCopyToClipboard]="input.value" (click)="activateCopiedMessage()" type="button" | 14 | *ngIf="withCopy" [cdkCopyToClipboard]="input.value" (click)="activateCopiedMessage()" type="button" |
15 | class="btn btn-outline-secondary" i18n-title title="Copy" | 15 | class="btn btn-outline-secondary text-uppercase" i18n-title title="Copy" |
16 | > | 16 | > |
17 | <span class="glyphicon glyphicon-copy"></span> | 17 | <span class="glyphicon glyphicon-duplicate"></span> |
18 | Copy | ||
18 | </button> | 19 | </button> |
19 | </div> | 20 | </div> |
20 | </div> | 21 | </div> |
diff --git a/client/src/app/shared/shared-forms/select/select-options.component.ts b/client/src/app/shared/shared-forms/select/select-options.component.ts index 2890670e5..8482b9dea 100644 --- a/client/src/app/shared/shared-forms/select/select-options.component.ts +++ b/client/src/app/shared/shared-forms/select/select-options.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, forwardRef, Input } from '@angular/core' | 1 | import { Component, forwardRef, HostListener, Input } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { SelectOptionsItem } from '../../../../types/select-options-item.model' | 3 | import { SelectOptionsItem } from '../../../../types/select-options-item.model' |
4 | 4 | ||
@@ -26,6 +26,13 @@ export class SelectOptionsComponent implements ControlValueAccessor { | |||
26 | 26 | ||
27 | propagateChange = (_: any) => { /* empty */ } | 27 | propagateChange = (_: any) => { /* empty */ } |
28 | 28 | ||
29 | // Allow plugins to update our value | ||
30 | @HostListener('change', [ '$event.target' ]) | ||
31 | handleChange (event: any) { | ||
32 | this.writeValue(event.value) | ||
33 | this.onModelChange() | ||
34 | } | ||
35 | |||
29 | writeValue (id: number | string) { | 36 | writeValue (id: number | string) { |
30 | this.selectedId = id | 37 | this.selectedId = id |
31 | } | 38 | } |
diff --git a/client/src/app/shared/shared-main/angular/autofocus.directive.ts b/client/src/app/shared/shared-main/angular/autofocus.directive.ts new file mode 100644 index 000000000..5f087d79d --- /dev/null +++ b/client/src/app/shared/shared-main/angular/autofocus.directive.ts | |||
@@ -0,0 +1,12 @@ | |||
1 | import { AfterViewInit, Directive, ElementRef } from '@angular/core' | ||
2 | |||
3 | @Directive({ | ||
4 | selector: '[autofocus]' | ||
5 | }) | ||
6 | export class AutofocusDirective implements AfterViewInit { | ||
7 | constructor (private host: ElementRef) { } | ||
8 | |||
9 | ngAfterViewInit () { | ||
10 | this.host.nativeElement.focus() | ||
11 | } | ||
12 | } | ||
diff --git a/client/src/app/shared/shared-main/angular/index.ts b/client/src/app/shared/shared-main/angular/index.ts index 29f8b3650..8ea47bb33 100644 --- a/client/src/app/shared/shared-main/angular/index.ts +++ b/client/src/app/shared/shared-main/angular/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './autofocus.directive' | ||
1 | export * from './bytes.pipe' | 2 | export * from './bytes.pipe' |
2 | export * from './duration-formatter.pipe' | 3 | export * from './duration-formatter.pipe' |
3 | export * from './from-now.pipe' | 4 | export * from './from-now.pipe' |
diff --git a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts index 3ddaffbdf..4fe3b964d 100644 --- a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts +++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts | |||
@@ -27,7 +27,9 @@ export class AuthInterceptor implements HttpInterceptor { | |||
27 | catchError((err: HttpErrorResponse) => { | 27 | catchError((err: HttpErrorResponse) => { |
28 | if (err.status === HttpStatusCode.UNAUTHORIZED_401 && err.error && err.error.code === 'invalid_token') { | 28 | if (err.status === HttpStatusCode.UNAUTHORIZED_401 && err.error && err.error.code === 'invalid_token') { |
29 | return this.handleTokenExpired(req, next) | 29 | return this.handleTokenExpired(req, next) |
30 | } else if (err.status === HttpStatusCode.UNAUTHORIZED_401) { | 30 | } |
31 | |||
32 | if (err.status === HttpStatusCode.UNAUTHORIZED_401) { | ||
31 | return this.handleNotAuthenticated(err) | 33 | return this.handleNotAuthenticated(err) |
32 | } | 34 | } |
33 | 35 | ||
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index 9d550996d..3e21d491a 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts | |||
@@ -19,6 +19,7 @@ import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' | |||
19 | import { SharedGlobalIconModule } from '../shared-icons' | 19 | import { SharedGlobalIconModule } from '../shared-icons' |
20 | import { AccountService, ActorAvatarInfoComponent, VideoAvatarChannelComponent } from './account' | 20 | import { AccountService, ActorAvatarInfoComponent, VideoAvatarChannelComponent } from './account' |
21 | import { | 21 | import { |
22 | AutofocusDirective, | ||
22 | BytesPipe, | 23 | BytesPipe, |
23 | DurationFormatterPipe, | 24 | DurationFormatterPipe, |
24 | FromNowPipe, | 25 | FromNowPipe, |
@@ -71,6 +72,7 @@ import { VideoChannelService } from './video-channel' | |||
71 | NumberFormatterPipe, | 72 | NumberFormatterPipe, |
72 | BytesPipe, | 73 | BytesPipe, |
73 | DurationFormatterPipe, | 74 | DurationFormatterPipe, |
75 | AutofocusDirective, | ||
74 | 76 | ||
75 | InfiniteScrollerDirective, | 77 | InfiniteScrollerDirective, |
76 | PeerTubeTemplateDirective, | 78 | PeerTubeTemplateDirective, |
@@ -125,6 +127,7 @@ import { VideoChannelService } from './video-channel' | |||
125 | BytesPipe, | 127 | BytesPipe, |
126 | NumberFormatterPipe, | 128 | NumberFormatterPipe, |
127 | DurationFormatterPipe, | 129 | DurationFormatterPipe, |
130 | AutofocusDirective, | ||
128 | 131 | ||
129 | InfiniteScrollerDirective, | 132 | InfiniteScrollerDirective, |
130 | PeerTubeTemplateDirective, | 133 | PeerTubeTemplateDirective, |
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts index 1211995fd..88a4811da 100644 --- a/client/src/app/shared/shared-main/users/user-notification.model.ts +++ b/client/src/app/shared/shared-main/users/user-notification.model.ts | |||
@@ -6,6 +6,7 @@ import { | |||
6 | AbuseState, | 6 | AbuseState, |
7 | ActorInfo, | 7 | ActorInfo, |
8 | FollowState, | 8 | FollowState, |
9 | PluginType, | ||
9 | UserNotification as UserNotificationServer, | 10 | UserNotification as UserNotificationServer, |
10 | UserNotificationType, | 11 | UserNotificationType, |
11 | UserRight, | 12 | UserRight, |
@@ -74,20 +75,40 @@ export class UserNotification implements UserNotificationServer { | |||
74 | } | 75 | } |
75 | } | 76 | } |
76 | 77 | ||
78 | plugin?: { | ||
79 | name: string | ||
80 | type: PluginType | ||
81 | latestVersion: string | ||
82 | } | ||
83 | |||
84 | peertube?: { | ||
85 | latestVersion: string | ||
86 | } | ||
87 | |||
77 | createdAt: string | 88 | createdAt: string |
78 | updatedAt: string | 89 | updatedAt: string |
79 | 90 | ||
80 | // Additional fields | 91 | // Additional fields |
81 | videoUrl?: string | 92 | videoUrl?: string |
82 | commentUrl?: any[] | 93 | commentUrl?: any[] |
94 | |||
83 | abuseUrl?: string | 95 | abuseUrl?: string |
84 | abuseQueryParams?: { [id: string]: string } = {} | 96 | abuseQueryParams?: { [id: string]: string } = {} |
97 | |||
85 | videoAutoBlacklistUrl?: string | 98 | videoAutoBlacklistUrl?: string |
99 | |||
86 | accountUrl?: string | 100 | accountUrl?: string |
101 | |||
87 | videoImportIdentifier?: string | 102 | videoImportIdentifier?: string |
88 | videoImportUrl?: string | 103 | videoImportUrl?: string |
104 | |||
89 | instanceFollowUrl?: string | 105 | instanceFollowUrl?: string |
90 | 106 | ||
107 | peertubeVersionLink?: string | ||
108 | |||
109 | pluginUrl?: string | ||
110 | pluginQueryParams?: { [id: string]: string } = {} | ||
111 | |||
91 | constructor (hash: UserNotificationServer, user: AuthUser) { | 112 | constructor (hash: UserNotificationServer, user: AuthUser) { |
92 | this.id = hash.id | 113 | this.id = hash.id |
93 | this.type = hash.type | 114 | this.type = hash.type |
@@ -114,6 +135,9 @@ export class UserNotification implements UserNotificationServer { | |||
114 | this.actorFollow = hash.actorFollow | 135 | this.actorFollow = hash.actorFollow |
115 | if (this.actorFollow) this.setAccountAvatarUrl(this.actorFollow.follower) | 136 | if (this.actorFollow) this.setAccountAvatarUrl(this.actorFollow.follower) |
116 | 137 | ||
138 | this.plugin = hash.plugin | ||
139 | this.peertube = hash.peertube | ||
140 | |||
117 | this.createdAt = hash.createdAt | 141 | this.createdAt = hash.createdAt |
118 | this.updatedAt = hash.updatedAt | 142 | this.updatedAt = hash.updatedAt |
119 | 143 | ||
@@ -197,6 +221,15 @@ export class UserNotification implements UserNotificationServer { | |||
197 | case UserNotificationType.AUTO_INSTANCE_FOLLOWING: | 221 | case UserNotificationType.AUTO_INSTANCE_FOLLOWING: |
198 | this.instanceFollowUrl = '/admin/follows/following-list' | 222 | this.instanceFollowUrl = '/admin/follows/following-list' |
199 | break | 223 | break |
224 | |||
225 | case UserNotificationType.NEW_PEERTUBE_VERSION: | ||
226 | this.peertubeVersionLink = 'https://joinpeertube.org/news' | ||
227 | break | ||
228 | |||
229 | case UserNotificationType.NEW_PLUGIN_VERSION: | ||
230 | this.pluginUrl = `/admin/plugins/list-installed` | ||
231 | this.pluginQueryParams.pluginType = this.plugin.type + '' | ||
232 | break | ||
200 | } | 233 | } |
201 | } catch (err) { | 234 | } catch (err) { |
202 | this.type = null | 235 | this.type = null |
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html index 265af8d55..325f0eaae 100644 --- a/client/src/app/shared/shared-main/users/user-notifications.component.html +++ b/client/src/app/shared/shared-main/users/user-notifications.component.html | |||
@@ -4,7 +4,7 @@ | |||
4 | <div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)"> | 4 | <div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)"> |
5 | 5 | ||
6 | <ng-container [ngSwitch]="notification.type"> | 6 | <ng-container [ngSwitch]="notification.type"> |
7 | <ng-container *ngSwitchCase="UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION"> | 7 | <ng-container *ngSwitchCase="1"> <!-- UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION --> |
8 | <ng-container *ngIf="notification.video; then hasVideo; else noVideo"></ng-container> | 8 | <ng-container *ngIf="notification.video; then hasVideo; else noVideo"></ng-container> |
9 | 9 | ||
10 | <ng-template #hasVideo> | 10 | <ng-template #hasVideo> |
@@ -26,7 +26,7 @@ | |||
26 | </ng-template> | 26 | </ng-template> |
27 | </ng-container> | 27 | </ng-container> |
28 | 28 | ||
29 | <ng-container *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO"> | 29 | <ng-container *ngSwitchCase="5"> <!-- UserNotificationType.UNBLACKLIST_ON_MY_VIDEO --> |
30 | <my-global-icon iconName="undo" aria-hidden="true"></my-global-icon> | 30 | <my-global-icon iconName="undo" aria-hidden="true"></my-global-icon> |
31 | 31 | ||
32 | <div class="message" i18n> | 32 | <div class="message" i18n> |
@@ -34,7 +34,7 @@ | |||
34 | </div> | 34 | </div> |
35 | </ng-container> | 35 | </ng-container> |
36 | 36 | ||
37 | <ng-container *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO"> | 37 | <ng-container *ngSwitchCase="4"> <!-- UserNotificationType.BLACKLIST_ON_MY_VIDEO --> |
38 | <my-global-icon iconName="no" aria-hidden="true"></my-global-icon> | 38 | <my-global-icon iconName="no" aria-hidden="true"></my-global-icon> |
39 | 39 | ||
40 | <div class="message" i18n> | 40 | <div class="message" i18n> |
@@ -42,7 +42,7 @@ | |||
42 | </div> | 42 | </div> |
43 | </ng-container> | 43 | </ng-container> |
44 | 44 | ||
45 | <ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS"> | 45 | <ng-container *ngSwitchCase="3"> <!-- UserNotificationType.NEW_ABUSE_FOR_MODERATORS --> |
46 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> | 46 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> |
47 | 47 | ||
48 | <div class="message" *ngIf="notification.videoUrl" i18n> | 48 | <div class="message" *ngIf="notification.videoUrl" i18n> |
@@ -63,7 +63,7 @@ | |||
63 | </div> | 63 | </div> |
64 | </ng-container> | 64 | </ng-container> |
65 | 65 | ||
66 | <ng-container *ngSwitchCase="UserNotificationType.ABUSE_STATE_CHANGE"> | 66 | <ng-container *ngSwitchCase="15"> <!-- UserNotificationType.ABUSE_STATE_CHANGE --> |
67 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> | 67 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> |
68 | 68 | ||
69 | <div class="message" i18n> | 69 | <div class="message" i18n> |
@@ -73,7 +73,7 @@ | |||
73 | </div> | 73 | </div> |
74 | </ng-container> | 74 | </ng-container> |
75 | 75 | ||
76 | <ng-container *ngSwitchCase="UserNotificationType.ABUSE_NEW_MESSAGE"> | 76 | <ng-container *ngSwitchCase="16"> <!-- UserNotificationType.ABUSE_NEW_MESSAGE --> |
77 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> | 77 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> |
78 | 78 | ||
79 | <div class="message" i18n> | 79 | <div class="message" i18n> |
@@ -81,7 +81,7 @@ | |||
81 | </div> | 81 | </div> |
82 | </ng-container> | 82 | </ng-container> |
83 | 83 | ||
84 | <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS"> | 84 | <ng-container *ngSwitchCase="12"> <!-- UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS --> |
85 | <my-global-icon iconName="no" aria-hidden="true"></my-global-icon> | 85 | <my-global-icon iconName="no" aria-hidden="true"></my-global-icon> |
86 | 86 | ||
87 | <div class="message" i18n> | 87 | <div class="message" i18n> |
@@ -89,7 +89,7 @@ | |||
89 | </div> | 89 | </div> |
90 | </ng-container> | 90 | </ng-container> |
91 | 91 | ||
92 | <ng-container *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO"> | 92 | <ng-container *ngSwitchCase="2"> |
93 | <ng-container *ngIf="notification.comment"> | 93 | <ng-container *ngIf="notification.comment"> |
94 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> | 94 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> |
95 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> | 95 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> |
@@ -109,7 +109,7 @@ | |||
109 | </ng-container> | 109 | </ng-container> |
110 | </ng-container> | 110 | </ng-container> |
111 | 111 | ||
112 | <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_PUBLISHED"> | 112 | <ng-container *ngSwitchCase="6"> <!-- UserNotificationType.MY_VIDEO_PUBLISHED --> |
113 | <my-global-icon iconName="film" aria-hidden="true"></my-global-icon> | 113 | <my-global-icon iconName="film" aria-hidden="true"></my-global-icon> |
114 | 114 | ||
115 | <div class="message" i18n> | 115 | <div class="message" i18n> |
@@ -117,7 +117,7 @@ | |||
117 | </div> | 117 | </div> |
118 | </ng-container> | 118 | </ng-container> |
119 | 119 | ||
120 | <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_SUCCESS"> | 120 | <ng-container *ngSwitchCase="7"> <!-- UserNotificationType.MY_VIDEO_IMPORT_SUCCESS --> |
121 | <my-global-icon iconName="cloud-download" aria-hidden="true"></my-global-icon> | 121 | <my-global-icon iconName="cloud-download" aria-hidden="true"></my-global-icon> |
122 | 122 | ||
123 | <div class="message" i18n> | 123 | <div class="message" i18n> |
@@ -125,7 +125,7 @@ | |||
125 | </div> | 125 | </div> |
126 | </ng-container> | 126 | </ng-container> |
127 | 127 | ||
128 | <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_ERROR"> | 128 | <ng-container *ngSwitchCase="8"> <!-- UserNotificationType.MY_VIDEO_IMPORT_ERROR --> |
129 | <my-global-icon iconName="cloud-error" aria-hidden="true"></my-global-icon> | 129 | <my-global-icon iconName="cloud-error" aria-hidden="true"></my-global-icon> |
130 | 130 | ||
131 | <div class="message" i18n> | 131 | <div class="message" i18n> |
@@ -133,7 +133,7 @@ | |||
133 | </div> | 133 | </div> |
134 | </ng-container> | 134 | </ng-container> |
135 | 135 | ||
136 | <ng-container *ngSwitchCase="UserNotificationType.NEW_USER_REGISTRATION"> | 136 | <ng-container *ngSwitchCase="9"> <!-- UserNotificationType.NEW_USER_REGISTRATION --> |
137 | <my-global-icon iconName="user-add" aria-hidden="true"></my-global-icon> | 137 | <my-global-icon iconName="user-add" aria-hidden="true"></my-global-icon> |
138 | 138 | ||
139 | <div class="message" i18n> | 139 | <div class="message" i18n> |
@@ -141,7 +141,7 @@ | |||
141 | </div> | 141 | </div> |
142 | </ng-container> | 142 | </ng-container> |
143 | 143 | ||
144 | <ng-container *ngSwitchCase="UserNotificationType.NEW_FOLLOW"> | 144 | <ng-container *ngSwitchCase="10"> <!-- UserNotificationType.NEW_FOLLOW --> |
145 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> | 145 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> |
146 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" /> | 146 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" /> |
147 | </a> | 147 | </a> |
@@ -154,7 +154,7 @@ | |||
154 | </div> | 154 | </div> |
155 | </ng-container> | 155 | </ng-container> |
156 | 156 | ||
157 | <ng-container *ngSwitchCase="UserNotificationType.COMMENT_MENTION"> | 157 | <ng-container *ngSwitchCase="11"> |
158 | <ng-container *ngIf="notification.comment"> | 158 | <ng-container *ngIf="notification.comment"> |
159 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> | 159 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> |
160 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> | 160 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> |
@@ -174,7 +174,7 @@ | |||
174 | </ng-container> | 174 | </ng-container> |
175 | </ng-container> | 175 | </ng-container> |
176 | 176 | ||
177 | <ng-container *ngSwitchCase="UserNotificationType.NEW_INSTANCE_FOLLOWER"> | 177 | <ng-container *ngSwitchCase="13"> <!-- UserNotificationType.NEW_INSTANCE_FOLLOWER --> |
178 | <my-global-icon iconName="users" aria-hidden="true"></my-global-icon> | 178 | <my-global-icon iconName="users" aria-hidden="true"></my-global-icon> |
179 | 179 | ||
180 | <div class="message" i18n> | 180 | <div class="message" i18n> |
@@ -183,7 +183,7 @@ | |||
183 | </div> | 183 | </div> |
184 | </ng-container> | 184 | </ng-container> |
185 | 185 | ||
186 | <ng-container *ngSwitchCase="UserNotificationType.AUTO_INSTANCE_FOLLOWING"> | 186 | <ng-container *ngSwitchCase="14"> <!-- UserNotificationType.AUTO_INSTANCE_FOLLOWING --> |
187 | <my-global-icon iconName="users" aria-hidden="true"></my-global-icon> | 187 | <my-global-icon iconName="users" aria-hidden="true"></my-global-icon> |
188 | 188 | ||
189 | <div class="message" i18n> | 189 | <div class="message" i18n> |
@@ -191,6 +191,22 @@ | |||
191 | </div> | 191 | </div> |
192 | </ng-container> | 192 | </ng-container> |
193 | 193 | ||
194 | <ng-container *ngSwitchCase="17"> <!-- UserNotificationType.NEW_PLUGIN_VERSION --> | ||
195 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> | ||
196 | |||
197 | <div class="message" i18n> | ||
198 | <a (click)="markAsRead(notification)" [routerLink]="notification.pluginUrl" [queryParams]="notification.pluginQueryParams">A new version of the plugin/theme {{ notification.plugin.name }}</a> is available: {{ notification.plugin.latestVersion }} | ||
199 | </div> | ||
200 | </ng-container> | ||
201 | |||
202 | <ng-container *ngSwitchCase="18"> <!-- UserNotificationType.NEW_PEERTUBE_VERSION --> | ||
203 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> | ||
204 | |||
205 | <div class="message" i18n> | ||
206 | <a (click)="markAsRead(notification)" [href]="notification.peertubeVersionLink" target="_blank" rel="noopener noreferer">A new version of PeerTube</a> is available: {{ notification.peertube.latestVersion }} | ||
207 | </div> | ||
208 | </ng-container> | ||
209 | |||
194 | <ng-container *ngSwitchDefault> | 210 | <ng-container *ngSwitchDefault> |
195 | <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> | 211 | <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> |
196 | 212 | ||
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.ts b/client/src/app/shared/shared-main/users/user-notifications.component.ts index 387c49d94..d7c722355 100644 --- a/client/src/app/shared/shared-main/users/user-notifications.component.ts +++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts | |||
@@ -21,9 +21,6 @@ export class UserNotificationsComponent implements OnInit { | |||
21 | notifications: UserNotification[] = [] | 21 | notifications: UserNotification[] = [] |
22 | sortField = 'createdAt' | 22 | sortField = 'createdAt' |
23 | 23 | ||
24 | // So we can access it in the template | ||
25 | UserNotificationType = UserNotificationType | ||
26 | |||
27 | componentPagination: ComponentPagination | 24 | componentPagination: ComponentPagination |
28 | 25 | ||
29 | onDataSubject = new Subject<any[]>() | 26 | onDataSubject = new Subject<any[]>() |
@@ -48,7 +45,7 @@ export class UserNotificationsComponent implements OnInit { | |||
48 | } | 45 | } |
49 | 46 | ||
50 | loadNotifications (reset?: boolean) { | 47 | loadNotifications (reset?: boolean) { |
51 | this.userNotificationService.listMyNotifications({ | 48 | const options = { |
52 | pagination: this.componentPagination, | 49 | pagination: this.componentPagination, |
53 | ignoreLoadingBar: this.ignoreLoadingBar, | 50 | ignoreLoadingBar: this.ignoreLoadingBar, |
54 | sort: { | 51 | sort: { |
@@ -56,7 +53,9 @@ export class UserNotificationsComponent implements OnInit { | |||
56 | // if we order by creation date, we want DESC. all other fields are ASC (like unread). | 53 | // if we order by creation date, we want DESC. all other fields are ASC (like unread). |
57 | order: this.sortField === 'createdAt' ? -1 : 1 | 54 | order: this.sortField === 'createdAt' ? -1 : 1 |
58 | } | 55 | } |
59 | }) | 56 | } |
57 | |||
58 | this.userNotificationService.listMyNotifications(options) | ||
60 | .subscribe( | 59 | .subscribe( |
61 | result => { | 60 | result => { |
62 | this.notifications = reset ? result.data : this.notifications.concat(result.data) | 61 | this.notifications = reset ? result.data : this.notifications.concat(result.data) |
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.html b/client/src/app/shared/shared-video-miniature/video-download.component.html index 4608e93e7..0e659fbe2 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.html +++ b/client/src/app/shared/shared-video-miniature/video-download.component.html | |||
@@ -36,7 +36,7 @@ | |||
36 | <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getLink()" /> | 36 | <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getLink()" /> |
37 | <div class="input-group-append" *ngIf="!isConfidentialVideo()"> | 37 | <div class="input-group-append" *ngIf="!isConfidentialVideo()"> |
38 | <button [cdkCopyToClipboard]="urlInput.value" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary"> | 38 | <button [cdkCopyToClipboard]="urlInput.value" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary"> |
39 | <span class="glyphicon glyphicon-copy"></span> | 39 | <span class="glyphicon glyphicon-duplicate"></span> |
40 | </button> | 40 | </button> |
41 | </div> | 41 | </div> |
42 | </div> | 42 | </div> |
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts index 90f4daf7c..e0b7b51ff 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { mapValues, pick } from 'lodash-es' | 1 | import { mapValues, pick } from 'lodash-es' |
2 | import { pipe } from 'rxjs' | ||
3 | import { tap } from 'rxjs/operators' | ||
2 | import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core' | 4 | import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core' |
3 | import { AuthService, Notifier } from '@app/core' | 5 | import { AuthService, HooksService, Notifier } from '@app/core' |
4 | import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
5 | import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models' | 7 | import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models' |
6 | import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main' | 8 | import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main' |
7 | 9 | ||
@@ -16,7 +18,7 @@ type FileMetadata = { [key: string]: { label: string, value: string }} | |||
16 | export class VideoDownloadComponent { | 18 | export class VideoDownloadComponent { |
17 | @ViewChild('modal', { static: true }) modal: ElementRef | 19 | @ViewChild('modal', { static: true }) modal: ElementRef |
18 | 20 | ||
19 | downloadType: 'direct' | 'torrent' = 'torrent' | 21 | downloadType: 'direct' | 'torrent' = 'direct' |
20 | resolutionId: number | string = -1 | 22 | resolutionId: number | string = -1 |
21 | subtitleLanguageId: string | 23 | subtitleLanguageId: string |
22 | 24 | ||
@@ -26,7 +28,7 @@ export class VideoDownloadComponent { | |||
26 | videoFileMetadataVideoStream: FileMetadata | undefined | 28 | videoFileMetadataVideoStream: FileMetadata | undefined |
27 | videoFileMetadataAudioStream: FileMetadata | undefined | 29 | videoFileMetadataAudioStream: FileMetadata | undefined |
28 | videoCaptions: VideoCaption[] | 30 | videoCaptions: VideoCaption[] |
29 | activeModal: NgbActiveModal | 31 | activeModal: NgbModalRef |
30 | 32 | ||
31 | type: DownloadType = 'video' | 33 | type: DownloadType = 'video' |
32 | 34 | ||
@@ -38,7 +40,8 @@ export class VideoDownloadComponent { | |||
38 | private notifier: Notifier, | 40 | private notifier: Notifier, |
39 | private modalService: NgbModal, | 41 | private modalService: NgbModal, |
40 | private videoService: VideoService, | 42 | private videoService: VideoService, |
41 | private auth: AuthService | 43 | private auth: AuthService, |
44 | private hooks: HooksService | ||
42 | ) { | 45 | ) { |
43 | this.bytesPipe = new BytesPipe() | 46 | this.bytesPipe = new BytesPipe() |
44 | this.numbersPipe = new NumberFormatterPipe(this.localeId) | 47 | this.numbersPipe = new NumberFormatterPipe(this.localeId) |
@@ -64,7 +67,12 @@ export class VideoDownloadComponent { | |||
64 | 67 | ||
65 | this.resolutionId = this.getVideoFiles()[0].resolution.id | 68 | this.resolutionId = this.getVideoFiles()[0].resolution.id |
66 | this.onResolutionIdChange() | 69 | this.onResolutionIdChange() |
70 | |||
67 | if (this.videoCaptions) this.subtitleLanguageId = this.videoCaptions[0].language.id | 71 | if (this.videoCaptions) this.subtitleLanguageId = this.videoCaptions[0].language.id |
72 | |||
73 | this.activeModal.shown.subscribe(() => { | ||
74 | this.hooks.runAction('action:modal.video-download.shown', 'common') | ||
75 | }) | ||
68 | } | 76 | } |
69 | 77 | ||
70 | onClose () { | 78 | onClose () { |
@@ -88,6 +96,7 @@ export class VideoDownloadComponent { | |||
88 | if (this.videoFile.metadata || !this.videoFile.metadataUrl) return | 96 | if (this.videoFile.metadata || !this.videoFile.metadataUrl) return |
89 | 97 | ||
90 | await this.hydrateMetadataFromMetadataUrl(this.videoFile) | 98 | await this.hydrateMetadataFromMetadataUrl(this.videoFile) |
99 | if (!this.videoFile.metadata) return | ||
91 | 100 | ||
92 | this.videoFileMetadataFormat = this.videoFile | 101 | this.videoFileMetadataFormat = this.videoFile |
93 | ? this.getMetadataFormat(this.videoFile.metadata.format) | 102 | ? this.getMetadataFormat(this.videoFile.metadata.format) |
@@ -201,7 +210,7 @@ export class VideoDownloadComponent { | |||
201 | 210 | ||
202 | private hydrateMetadataFromMetadataUrl (file: VideoFile) { | 211 | private hydrateMetadataFromMetadataUrl (file: VideoFile) { |
203 | const observable = this.videoService.getVideoFileMetadata(file.metadataUrl) | 212 | const observable = this.videoService.getVideoFileMetadata(file.metadataUrl) |
204 | observable.subscribe(res => file.metadata = res) | 213 | .pipe(tap(res => file.metadata = res)) |
205 | 214 | ||
206 | return observable.toPromise() | 215 | return observable.toPromise() |
207 | } | 216 | } |