diff options
Diffstat (limited to 'client/src/app/shared')
11 files changed, 115 insertions, 32 deletions
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 | } |