diff options
22 files changed, 138 insertions, 109 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html index 48b5681f4..ce603459e 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.html +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html | |||
@@ -44,8 +44,8 @@ | |||
44 | 44 | ||
45 | <td class="action-cell"> | 45 | <td class="action-cell"> |
46 | <ng-container *ngIf="follow.state === 'pending'"> | 46 | <ng-container *ngIf="follow.state === 'pending'"> |
47 | <my-button i18n-label label="Accept" icon="tick" (click)="acceptFollower(follow)"></my-button> | 47 | <my-button i18n-title title="Accept" icon="tick" (click)="acceptFollower(follow)"></my-button> |
48 | <my-button i18n-label label="Refuse" icon="cross" (click)="rejectFollower(follow)"></my-button> | 48 | <my-button i18n-title title="Refuse" icon="cross" (click)="rejectFollower(follow)"></my-button> |
49 | </ng-container> | 49 | </ng-container> |
50 | 50 | ||
51 | <my-delete-button *ngIf="follow.state === 'accepted'" (click)="deleteFollower(follow)"></my-delete-button> | 51 | <my-delete-button *ngIf="follow.state === 'accepted'" (click)="deleteFollower(follow)"></my-delete-button> |
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html index 2e7b322ca..d103f8e2f 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.component.html +++ b/client/src/app/+admin/users/user-edit/user-edit.component.html | |||
@@ -95,7 +95,7 @@ | |||
95 | <input | 95 | <input |
96 | type="text" id="email" i18n-placeholder placeholder="mail@example.com" class="form-control" | 96 | type="text" id="email" i18n-placeholder placeholder="mail@example.com" class="form-control" |
97 | formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }" | 97 | formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }" |
98 | autocomplete="off" [readonly]="user.pluginAuth !== null" | 98 | autocomplete="off" [readonly]="user && user.pluginAuth !== null" |
99 | > | 99 | > |
100 | <div *ngIf="formErrors.email" class="form-error"> | 100 | <div *ngIf="formErrors.email" class="form-error"> |
101 | {{ formErrors.email }} | 101 | {{ formErrors.email }} |
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html index 3e07550c1..4caa076a3 100644 --- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html +++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html | |||
@@ -45,7 +45,7 @@ | |||
45 | <td>{{ videoImport.createdAt | date: 'short' }}</td> | 45 | <td>{{ videoImport.createdAt | date: 'short' }}</td> |
46 | 46 | ||
47 | <td class="action-cell"> | 47 | <td class="action-cell"> |
48 | <my-edit-button *ngIf="isVideoImportSuccess(videoImport) && videoImport.video" [routerLink]="getEditVideoUrl(videoImport.video)"></my-edit-button> | 48 | <my-edit-button label=" " *ngIf="isVideoImportSuccess(videoImport) && videoImport.video" [routerLink]="getEditVideoUrl(videoImport.video)"></my-edit-button> |
49 | </td> | 49 | </td> |
50 | </tr> | 50 | </tr> |
51 | </ng-template> | 51 | </ng-template> |
diff --git a/client/src/app/+search/channel-lazy-load.resolver.ts b/client/src/app/+search/channel-lazy-load.resolver.ts index 17a212829..d9f7ec901 100644 --- a/client/src/app/+search/channel-lazy-load.resolver.ts +++ b/client/src/app/+search/channel-lazy-load.resolver.ts | |||
@@ -12,20 +12,12 @@ export class ChannelLazyLoadResolver implements Resolve<any> { | |||
12 | 12 | ||
13 | resolve (route: ActivatedRouteSnapshot) { | 13 | resolve (route: ActivatedRouteSnapshot) { |
14 | const url = route.params.url | 14 | const url = route.params.url |
15 | const externalRedirect = route.params.externalRedirect | ||
16 | const fromPath = route.params.fromPath | ||
17 | 15 | ||
18 | if (!url) { | 16 | if (!url) { |
19 | console.error('Could not find url param.', { params: route.params }) | 17 | console.error('Could not find url param.', { params: route.params }) |
20 | return this.router.navigateByUrl('/404') | 18 | return this.router.navigateByUrl('/404') |
21 | } | 19 | } |
22 | 20 | ||
23 | if (externalRedirect === 'true') { | ||
24 | window.open(url) | ||
25 | this.router.navigateByUrl(fromPath) | ||
26 | return | ||
27 | } | ||
28 | |||
29 | return this.searchService.searchVideoChannels({ search: url }) | 21 | return this.searchService.searchVideoChannels({ search: url }) |
30 | .pipe( | 22 | .pipe( |
31 | map(result => { | 23 | map(result => { |
diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html index 9bff024ad..84be4fb14 100644 --- a/client/src/app/+search/search.component.html +++ b/client/src/app/+search/search.component.html | |||
@@ -35,15 +35,27 @@ | |||
35 | 35 | ||
36 | <ng-container *ngFor="let result of results"> | 36 | <ng-container *ngFor="let result of results"> |
37 | <div *ngIf="isVideoChannel(result)" class="entry video-channel"> | 37 | <div *ngIf="isVideoChannel(result)" class="entry video-channel"> |
38 | <a [routerLink]="getChannelUrl(result)"> | 38 | <a *ngIf="!isExternalChannelUrl()" [routerLink]="getChannelUrl(result)"> |
39 | <img [src]="result.avatarUrl" alt="Avatar" /> | ||
40 | </a> | ||
41 | |||
42 | <a *ngIf="isExternalChannelUrl()" [href]="getChannelUrl(result)" target="_blank"> | ||
39 | <img [src]="result.avatarUrl" alt="Avatar" /> | 43 | <img [src]="result.avatarUrl" alt="Avatar" /> |
40 | </a> | 44 | </a> |
41 | 45 | ||
42 | <div class="video-channel-info"> | 46 | <div class="video-channel-info"> |
43 | <a [routerLink]="getChannelUrl(result)" class="video-channel-names"> | 47 | <a *ngIf="!isExternalChannelUrl()" [routerLink]="getChannelUrl(result)" class="video-channel-names"> |
48 | <ng-container *ngTemplateOutlet="aContent"></ng-container> | ||
49 | </a> | ||
50 | |||
51 | <a *ngIf="isExternalChannelUrl()" [href]="getChannelUrl(result)" target="_blank" class="video-channel-names"> | ||
52 | <ng-container *ngTemplateOutlet="aContent"></ng-container> | ||
53 | </a> | ||
54 | |||
55 | <ng-template #aContent> | ||
44 | <div class="video-channel-display-name">{{ result.displayName }}</div> | 56 | <div class="video-channel-display-name">{{ result.displayName }}</div> |
45 | <div class="video-channel-name">{{ result.nameWithHost }}</div> | 57 | <div class="video-channel-name">{{ result.nameWithHost }}</div> |
46 | </a> | 58 | </ng-template> |
47 | 59 | ||
48 | <div i18n class="video-channel-followers">{{ result.followersCount }} subscribers</div> | 60 | <div i18n class="video-channel-followers">{{ result.followersCount }} subscribers</div> |
49 | </div> | 61 | </div> |
@@ -54,7 +66,7 @@ | |||
54 | <div *ngIf="isVideo(result)" class="entry video"> | 66 | <div *ngIf="isVideo(result)" class="entry video"> |
55 | <my-video-miniature | 67 | <my-video-miniature |
56 | [video]="result" [user]="userMiniature" [displayAsRow]="true" [displayVideoActions]="!hideActions()" | 68 | [video]="result" [user]="userMiniature" [displayAsRow]="true" [displayVideoActions]="!hideActions()" |
57 | [displayOptions]="videoDisplayOptions" [useLazyLoadUrl]="advancedSearch.searchTarget === 'search-index'" | 69 | [displayOptions]="videoDisplayOptions" [videoLinkType]="getVideoLinkType()" |
58 | (videoBlocked)="removeVideoFromArray(result)" (videoRemoved)="removeVideoFromArray(result)" | 70 | (videoBlocked)="removeVideoFromArray(result)" (videoRemoved)="removeVideoFromArray(result)" |
59 | ></my-video-miniature> | 71 | ></my-video-miniature> |
60 | </div> | 72 | </div> |
diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts index 1ed54937b..70116fab3 100644 --- a/client/src/app/+search/search.component.ts +++ b/client/src/app/+search/search.component.ts | |||
@@ -5,7 +5,7 @@ import { AuthService, ComponentPagination, HooksService, Notifier, ServerService | |||
5 | import { immutableAssign } from '@app/helpers' | 5 | import { immutableAssign } from '@app/helpers' |
6 | import { Video, VideoChannel } from '@app/shared/shared-main' | 6 | import { Video, VideoChannel } from '@app/shared/shared-main' |
7 | import { AdvancedSearch, SearchService } from '@app/shared/shared-search' | 7 | import { AdvancedSearch, SearchService } from '@app/shared/shared-search' |
8 | import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' | 8 | import { MiniatureDisplayOptions, VideoLinkType } from '@app/shared/shared-video-miniature' |
9 | import { MetaService } from '@ngx-meta/core' | 9 | import { MetaService } from '@ngx-meta/core' |
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { SearchTargetType, ServerConfig } from '@shared/models' | 11 | import { SearchTargetType, ServerConfig } from '@shared/models' |
@@ -119,6 +119,25 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
119 | return this.authService.isLoggedIn() | 119 | return this.authService.isLoggedIn() |
120 | } | 120 | } |
121 | 121 | ||
122 | getVideoLinkType (): VideoLinkType { | ||
123 | if (this.advancedSearch.searchTarget === 'search-index') { | ||
124 | const remoteUriConfig = this.serverConfig.search.remoteUri | ||
125 | |||
126 | // Redirect on the external instance if not allowed to fetch remote data | ||
127 | if ((!this.isUserLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users) { | ||
128 | return 'external' | ||
129 | } | ||
130 | |||
131 | return 'lazy-load' | ||
132 | } | ||
133 | |||
134 | return 'internal' | ||
135 | } | ||
136 | |||
137 | isExternalChannelUrl () { | ||
138 | return this.getVideoLinkType() === 'external' | ||
139 | } | ||
140 | |||
122 | search () { | 141 | search () { |
123 | forkJoin([ | 142 | forkJoin([ |
124 | this.getVideosObs(), | 143 | this.getVideosObs(), |
@@ -184,17 +203,16 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
184 | } | 203 | } |
185 | 204 | ||
186 | getChannelUrl (channel: VideoChannel) { | 205 | getChannelUrl (channel: VideoChannel) { |
187 | if (this.advancedSearch.searchTarget === 'search-index' && channel.url) { | 206 | // Same algorithm than videos |
188 | const remoteUriConfig = this.serverConfig.search.remoteUri | 207 | if (this.getVideoLinkType() === 'external') { |
189 | 208 | return channel.url | |
190 | // Redirect on the external instance if not allowed to fetch remote data | 209 | } |
191 | const externalRedirect = (!this.authService.isLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users | ||
192 | const fromPath = window.location.pathname + window.location.search | ||
193 | 210 | ||
194 | return [ '/search/lazy-load-channel', { url: channel.url, externalRedirect, fromPath } ] | 211 | if (this.getVideoLinkType() === 'internal') { |
212 | return [ '/video-channels', channel.nameWithHost ] | ||
195 | } | 213 | } |
196 | 214 | ||
197 | return [ '/video-channels', channel.nameWithHost ] | 215 | return [ '/search/lazy-load-channel', { url: channel.url } ] |
198 | } | 216 | } |
199 | 217 | ||
200 | hideActions () { | 218 | hideActions () { |
diff --git a/client/src/app/+search/video-lazy-load.resolver.ts b/client/src/app/+search/video-lazy-load.resolver.ts index e8b2b8c74..d4fe6ed79 100644 --- a/client/src/app/+search/video-lazy-load.resolver.ts +++ b/client/src/app/+search/video-lazy-load.resolver.ts | |||
@@ -12,20 +12,12 @@ export class VideoLazyLoadResolver implements Resolve<any> { | |||
12 | 12 | ||
13 | resolve (route: ActivatedRouteSnapshot) { | 13 | resolve (route: ActivatedRouteSnapshot) { |
14 | const url = route.params.url | 14 | const url = route.params.url |
15 | const externalRedirect = route.params.externalRedirect | ||
16 | const fromPath = route.params.fromPath | ||
17 | 15 | ||
18 | if (!url) { | 16 | if (!url) { |
19 | console.error('Could not find url param.', { params: route.params }) | 17 | console.error('Could not find url param.', { params: route.params }) |
20 | return this.router.navigateByUrl('/404') | 18 | return this.router.navigateByUrl('/404') |
21 | } | 19 | } |
22 | 20 | ||
23 | if (externalRedirect === 'true') { | ||
24 | window.open(url) | ||
25 | this.router.navigateByUrl(fromPath) | ||
26 | return | ||
27 | } | ||
28 | |||
29 | return this.searchService.searchVideos({ search: url }) | 21 | return this.searchService.searchVideos({ search: url }) |
30 | .pipe( | 22 | .pipe( |
31 | map(result => { | 23 | map(result => { |
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.scss b/client/src/app/shared/shared-forms/markdown-textarea.component.scss index f2c76f7a1..39e72b5bc 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.scss +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.scss | |||
@@ -43,7 +43,7 @@ $input-border-radius: 3px; | |||
43 | } | 43 | } |
44 | 44 | ||
45 | .grey-button { | 45 | .grey-button { |
46 | padding: 0 12px 0 12px; | 46 | padding: 0 7px 0 12px; |
47 | } | 47 | } |
48 | } | 48 | } |
49 | } | 49 | } |
diff --git a/client/src/app/shared/shared-main/buttons/button.component.html b/client/src/app/shared/shared-main/buttons/button.component.html index a7dd4bb10..8eccd5c3c 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.html +++ b/client/src/app/shared/shared-main/buttons/button.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <span class="action-button" [ngClass]="getClasses()" [title]="getTitle()"> | 1 | <span class="action-button" [ngClass]="getClasses()" [title]="getTitle()" tabindex="0"> |
2 | <my-global-icon *ngIf="!loading" [iconName]="icon"></my-global-icon> | 2 | <my-global-icon *ngIf="!loading" [iconName]="icon"></my-global-icon> |
3 | <my-small-loader [loading]="loading"></my-small-loader> | 3 | <my-small-loader [loading]="loading"></my-small-loader> |
4 | 4 | ||
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.html b/client/src/app/shared/shared-main/buttons/delete-button.component.html index 398b6db1e..6643e6013 100644 --- a/client/src/app/shared/shared-main/buttons/delete-button.component.html +++ b/client/src/app/shared/shared-main/buttons/delete-button.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <span class="action-button action-button-delete grey-button" [title]="title" role="button"> | 1 | <span class="action-button action-button-delete grey-button" [title]="title" role="button" tabindex="0"> |
2 | <my-global-icon iconName="delete" aria-hidden="true"></my-global-icon> | 2 | <my-global-icon iconName="delete" aria-hidden="true"></my-global-icon> |
3 | 3 | ||
4 | <span class="button-label" *ngIf="label">{{ label }}</span> | 4 | <span class="button-label" *ngIf="label">{{ label }}</span> |
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html index fe5510c56..0cb0f321b 100644 --- a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html | |||
@@ -1,7 +1,12 @@ | |||
1 | <a | 1 | <a *ngIf="!videoHref" [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" class="video-thumbnail"> |
2 | [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" | 2 | <ng-container *ngTemplateOutlet="aContent"></ng-container> |
3 | class="video-thumbnail" | 3 | </a> |
4 | > | 4 | |
5 | <a *ngIf="videoHref" [href]="videoHref" [target]="videoTarget" class="video-thumbnail"> | ||
6 | <ng-container *ngTemplateOutlet="aContent"></ng-container> | ||
7 | </a> | ||
8 | |||
9 | <ng-template #aContent> | ||
5 | <img alt="" [attr.aria-label]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> | 10 | <img alt="" [attr.aria-label]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> |
6 | 11 | ||
7 | <div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay"> | 12 | <div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay"> |
@@ -30,4 +35,4 @@ | |||
30 | <div class="progress-bar" *ngIf="video.userHistory?.currentTime"> | 35 | <div class="progress-bar" *ngIf="video.userHistory?.currentTime"> |
31 | <div [ngStyle]="{ 'width.%': getProgressPercent() }"></div> | 36 | <div [ngStyle]="{ 'width.%': getProgressPercent() }"></div> |
32 | </div> | 37 | </div> |
33 | </a> | 38 | </ng-template> |
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts index 3ff45d9b7..812c7a508 100644 --- a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts | |||
@@ -11,8 +11,11 @@ import { Video } from '../shared-main' | |||
11 | export class VideoThumbnailComponent { | 11 | export class VideoThumbnailComponent { |
12 | @Input() video: Video | 12 | @Input() video: Video |
13 | @Input() nsfw = false | 13 | @Input() nsfw = false |
14 | @Input() routerLink: any[] | 14 | |
15 | @Input() videoRouterLink: any[] | ||
15 | @Input() queryParams: { [ p: string ]: any } | 16 | @Input() queryParams: { [ p: string ]: any } |
17 | @Input() videoHref: string | ||
18 | @Input() videoTarget: string | ||
16 | 19 | ||
17 | @Input() displayWatchLaterPlaylist: boolean | 20 | @Input() displayWatchLaterPlaylist: boolean |
18 | @Input() inWatchLaterPlaylist: boolean | 21 | @Input() inWatchLaterPlaylist: boolean |
@@ -49,7 +52,7 @@ export class VideoThumbnailComponent { | |||
49 | } | 52 | } |
50 | 53 | ||
51 | getVideoRouterLink () { | 54 | getVideoRouterLink () { |
52 | if (this.routerLink) return this.routerLink | 55 | if (this.videoRouterLink) return this.videoRouterLink |
53 | 56 | ||
54 | return [ '/videos/watch', this.video.uuid ] | 57 | return [ '/videos/watch', this.video.uuid ] |
55 | } | 58 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html index e5d91e69a..a88b3bc9e 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.html +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow, 'fit-width': fitWidth }" (mouseenter)="loadActions()"> | 1 | <div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow, 'fit-width': fitWidth }" (mouseenter)="loadActions()"> |
2 | <my-video-thumbnail | 2 | <my-video-thumbnail |
3 | [video]="video" [nsfw]="isVideoBlur" [routerLink]="videoLink" | 3 | [video]="video" [nsfw]="isVideoBlur" [videoRouterLink]="videoRouterLink" [videoHref]="videoHref" [videoTarget]="videoTarget" |
4 | [displayWatchLaterPlaylist]="isWatchLaterPlaylistDisplayed()" [inWatchLaterPlaylist]="inWatchLaterPlaylist" (watchLaterClick)="onWatchLaterClick($event)" | 4 | [displayWatchLaterPlaylist]="isWatchLaterPlaylistDisplayed()" [inWatchLaterPlaylist]="inWatchLaterPlaylist" (watchLaterClick)="onWatchLaterClick($event)" |
5 | > | 5 | > |
6 | <ng-container ngProjectAs="label-warning" *ngIf="displayOptions.privacyLabel && isUnlistedVideo()" i18n>Unlisted</ng-container> | 6 | <ng-container ngProjectAs="label-warning" *ngIf="displayOptions.privacyLabel && isUnlistedVideo()" i18n>Unlisted</ng-container> |
@@ -15,10 +15,12 @@ | |||
15 | </a> | 15 | </a> |
16 | 16 | ||
17 | <div class="w-100 d-flex flex-column"> | 17 | <div class="w-100 d-flex flex-column"> |
18 | <a | 18 | <a *ngIf="!videoHref" tabindex="-1" class="video-miniature-name" |
19 | tabindex="-1" | 19 | [routerLink]="videoRouterLink" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }" |
20 | class="video-miniature-name" | 20 | >{{ video.name }}</a> |
21 | [routerLink]="videoLink" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }" | 21 | |
22 | <a *ngIf="videoHref" tabindex="-1" class="video-miniature-name" | ||
23 | [href]="videoHref" [target]="videoTarget" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }" | ||
22 | >{{ video.name }}</a> | 24 | >{{ video.name }}</a> |
23 | 25 | ||
24 | <span class="video-miniature-created-at-views"> | 26 | <span class="video-miniature-created-at-views"> |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index e1adbb6ad..f434550dd 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts | |||
@@ -29,6 +29,7 @@ export type MiniatureDisplayOptions = { | |||
29 | blacklistInfo?: boolean | 29 | blacklistInfo?: boolean |
30 | nsfw?: boolean | 30 | nsfw?: boolean |
31 | } | 31 | } |
32 | export type VideoLinkType = 'internal' | 'lazy-load' | 'external' | ||
32 | 33 | ||
33 | @Component({ | 34 | @Component({ |
34 | selector: 'my-video-miniature', | 35 | selector: 'my-video-miniature', |
@@ -55,7 +56,7 @@ export class VideoMiniatureComponent implements OnInit { | |||
55 | @Input() displayVideoActions = true | 56 | @Input() displayVideoActions = true |
56 | @Input() fitWidth = false | 57 | @Input() fitWidth = false |
57 | 58 | ||
58 | @Input() useLazyLoadUrl = false | 59 | @Input() videoLinkType: VideoLinkType = 'internal' |
59 | 60 | ||
60 | @Output() videoBlocked = new EventEmitter() | 61 | @Output() videoBlocked = new EventEmitter() |
61 | @Output() videoUnblocked = new EventEmitter() | 62 | @Output() videoUnblocked = new EventEmitter() |
@@ -85,7 +86,9 @@ export class VideoMiniatureComponent implements OnInit { | |||
85 | playlistElementId?: number | 86 | playlistElementId?: number |
86 | } | 87 | } |
87 | 88 | ||
88 | videoLink: any[] = [] | 89 | videoRouterLink: any[] = [] |
90 | videoHref: string | ||
91 | videoTarget: string | ||
89 | 92 | ||
90 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' | 93 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' |
91 | 94 | ||
@@ -125,18 +128,20 @@ export class VideoMiniatureComponent implements OnInit { | |||
125 | } | 128 | } |
126 | 129 | ||
127 | buildVideoLink () { | 130 | buildVideoLink () { |
128 | if (this.useLazyLoadUrl && this.video.url) { | 131 | if (this.videoLinkType === 'internal' || !this.video.url) { |
129 | const remoteUriConfig = this.serverConfig.search.remoteUri | 132 | this.videoRouterLink = [ '/videos/watch', this.video.uuid ] |
130 | 133 | return | |
131 | // Redirect on the external instance if not allowed to fetch remote data | 134 | } |
132 | const externalRedirect = (!this.authService.isLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users | ||
133 | const fromPath = window.location.pathname + window.location.search | ||
134 | 135 | ||
135 | this.videoLink = [ '/search/lazy-load-video', { url: this.video.url, externalRedirect, fromPath } ] | 136 | if (this.videoLinkType === 'external') { |
137 | this.videoRouterLink = null | ||
138 | this.videoHref = this.video.url | ||
139 | this.videoTarget = '_blank' | ||
136 | return | 140 | return |
137 | } | 141 | } |
138 | 142 | ||
139 | this.videoLink = [ '/videos/watch', this.video.uuid ] | 143 | // Lazy load |
144 | this.videoRouterLink = [ '/search/lazy-load-video', { url: this.video.url } ] | ||
140 | } | 145 | } |
141 | 146 | ||
142 | displayOwnerAccount () { | 147 | displayOwnerAccount () { |
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.scss b/client/src/app/shared/shared-video-miniature/videos-selection.component.scss index d3cbabf23..c33e11889 100644 --- a/client/src/app/shared/shared-video-miniature/videos-selection.component.scss +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.scss | |||
@@ -10,14 +10,13 @@ | |||
10 | position: fixed; | 10 | position: fixed; |
11 | 11 | ||
12 | .action-button { | 12 | .action-button { |
13 | display: inline-block; | 13 | display: block; |
14 | margin-left: 55px; | ||
14 | } | 15 | } |
15 | 16 | ||
16 | .action-button-cancel-selection { | 17 | .action-button-cancel-selection { |
17 | @include peertube-button; | 18 | @include peertube-button; |
18 | @include grey-button; | 19 | @include grey-button; |
19 | |||
20 | margin-right: 10px; | ||
21 | } | 20 | } |
22 | } | 21 | } |
23 | } | 22 | } |
@@ -54,4 +53,8 @@ | |||
54 | margin-top: 10px; | 53 | margin-top: 10px; |
55 | } | 54 | } |
56 | } | 55 | } |
56 | |||
57 | .action-selection-mode { | ||
58 | display: none; // disable for small screens | ||
59 | } | ||
57 | } | 60 | } |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html index e3f7ef017..473fdc102 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html | |||
@@ -8,7 +8,7 @@ | |||
8 | <my-video-thumbnail | 8 | <my-video-thumbnail |
9 | *ngIf="playlistElement.video" | 9 | *ngIf="playlistElement.video" |
10 | [video]="playlistElement.video" [nsfw]="isVideoBlur(playlistElement.video)" | 10 | [video]="playlistElement.video" [nsfw]="isVideoBlur(playlistElement.video)" |
11 | [routerLink]="buildRouterLink()" [queryParams]="buildRouterQuery()" | 11 | [videoRouterLink]="buildRouterLink()" [queryParams]="buildRouterQuery()" |
12 | ></my-video-thumbnail> | 12 | ></my-video-thumbnail> |
13 | 13 | ||
14 | <div class="fake-thumbnail" *ngIf="!playlistElement.video"></div> | 14 | <div class="fake-thumbnail" *ngIf="!playlistElement.video"></div> |
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts index 46c6bbaf2..f8116f4bc 100644 --- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts | |||
@@ -43,19 +43,23 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
43 | 43 | ||
44 | // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 | 44 | // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 |
45 | if (!(videojs as any).Html5Hlsjs) { | 45 | if (!(videojs as any).Html5Hlsjs) { |
46 | const message = 'HLS.js does not seem to be supported.' | 46 | console.warn('HLS.js does not seem to be supported. Try to fallback to built in HLS.') |
47 | console.warn(message) | ||
48 | 47 | ||
49 | player.ready(() => player.trigger('error', new Error(message))) | 48 | if (!player.canPlayType('application/vnd.apple.mpegurl')) { |
50 | return | 49 | const message = 'Cannot fallback to built-in HLS' |
51 | } | 50 | console.warn(message) |
52 | 51 | ||
53 | // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 | 52 | player.ready(() => player.trigger('error', new Error(message))) |
54 | (videojs as any).Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => { | 53 | return |
55 | this.hlsjs = hlsjs | 54 | } |
56 | }) | 55 | } else { |
56 | // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 | ||
57 | (videojs as any).Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => { | ||
58 | this.hlsjs = hlsjs | ||
59 | }) | ||
57 | 60 | ||
58 | initVideoJsContribHlsJsPlayer(player) | 61 | initVideoJsContribHlsJsPlayer(player) |
62 | } | ||
59 | 63 | ||
60 | this.startTime = timeToInt(options.startTime) | 64 | this.startTime = timeToInt(options.startTime) |
61 | 65 | ||
@@ -64,11 +68,13 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
64 | src: options.src | 68 | src: options.src |
65 | }) | 69 | }) |
66 | 70 | ||
67 | player.one('play', () => { | 71 | player.ready(() => { |
68 | player.addClass('vjs-has-big-play-button-clicked') | 72 | this.initializeCore() |
69 | }) | ||
70 | 73 | ||
71 | player.ready(() => this.initialize()) | 74 | if ((videojs as any).Html5Hlsjs) { |
75 | this.initializePlugin() | ||
76 | } | ||
77 | }) | ||
72 | } | 78 | } |
73 | 79 | ||
74 | dispose () { | 80 | dispose () { |
@@ -82,7 +88,19 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
82 | return this.hlsjs | 88 | return this.hlsjs |
83 | } | 89 | } |
84 | 90 | ||
85 | private initialize () { | 91 | private initializeCore () { |
92 | this.player.one('play', () => { | ||
93 | this.player.addClass('vjs-has-big-play-button-clicked') | ||
94 | }) | ||
95 | |||
96 | this.player.one('canplay', () => { | ||
97 | if (this.startTime) { | ||
98 | this.player.currentTime(this.startTime) | ||
99 | } | ||
100 | }) | ||
101 | } | ||
102 | |||
103 | private initializePlugin () { | ||
86 | initHlsJsPlayer(this.hlsjs) | 104 | initHlsJsPlayer(this.hlsjs) |
87 | 105 | ||
88 | // FIXME: typings | 106 | // FIXME: typings |
@@ -102,12 +120,6 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
102 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() | 120 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() |
103 | 121 | ||
104 | this.runStats() | 122 | this.runStats() |
105 | |||
106 | this.player.one('canplay', () => { | ||
107 | if (this.startTime) { | ||
108 | this.player.currentTime(this.startTime) | ||
109 | } | ||
110 | }) | ||
111 | } | 123 | } |
112 | 124 | ||
113 | private runStats () { | 125 | private runStats () { |
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index a9a950dc0..2388c0469 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss | |||
@@ -79,15 +79,6 @@ p-table { | |||
79 | tr { | 79 | tr { |
80 | &:hover { | 80 | &:hover { |
81 | background-color: pvar(--submenuColor) !important; | 81 | background-color: pvar(--submenuColor) !important; |
82 | |||
83 | .action-cell { | ||
84 | .dropdown-root, | ||
85 | my-edit-button, | ||
86 | my-delete-button, | ||
87 | my-button { | ||
88 | display: inline-block !important; | ||
89 | } | ||
90 | } | ||
91 | } | 82 | } |
92 | 83 | ||
93 | td { | 84 | td { |
@@ -164,18 +155,9 @@ p-table { | |||
164 | my-edit-button, | 155 | my-edit-button, |
165 | my-delete-button, | 156 | my-delete-button, |
166 | my-button { | 157 | my-button { |
167 | display: none !important; | 158 | display: inline-block !important; |
168 | margin-left: 5px; | 159 | margin-left: 5px; |
169 | 160 | ||
170 | &.show { | ||
171 | display: inline-block !important; | ||
172 | } | ||
173 | |||
174 | // keep displaying on touchscreen | ||
175 | @media not all and (hover: hover) and (pointer: fine) { | ||
176 | display: inline-block !important; | ||
177 | } | ||
178 | |||
179 | :first-child { | 161 | :first-child { |
180 | margin-left: 0 | 162 | margin-left: 0 |
181 | } | 163 | } |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index fcd828ae3..5939f6125 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -340,7 +340,7 @@ async function askResetUserPassword (req: express.Request, res: express.Response | |||
340 | 340 | ||
341 | const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id) | 341 | const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id) |
342 | const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString | 342 | const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString |
343 | await Emailer.Instance.addPasswordResetEmailJob(user.email, url) | 343 | await Emailer.Instance.addPasswordResetEmailJob(user.username, user.email, url) |
344 | 344 | ||
345 | return res.status(204).end() | 345 | return res.status(204).end() |
346 | } | 346 | } |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index d54eab966..48ba7421e 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -427,12 +427,13 @@ class Emailer { | |||
427 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 427 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
428 | } | 428 | } |
429 | 429 | ||
430 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { | 430 | addPasswordResetEmailJob (username: string, to: string, resetPasswordUrl: string) { |
431 | const emailPayload: EmailPayload = { | 431 | const emailPayload: EmailPayload = { |
432 | template: 'password-reset', | 432 | template: 'password-reset', |
433 | to: [ to ], | 433 | to: [ to ], |
434 | subject: 'Reset your account password', | 434 | subject: 'Reset your account password', |
435 | locals: { | 435 | locals: { |
436 | username, | ||
436 | resetPasswordUrl | 437 | resetPasswordUrl |
437 | } | 438 | } |
438 | } | 439 | } |
@@ -454,12 +455,13 @@ class Emailer { | |||
454 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 455 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
455 | } | 456 | } |
456 | 457 | ||
457 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | 458 | addVerifyEmailJob (username: string, to: string, verifyEmailUrl: string) { |
458 | const emailPayload: EmailPayload = { | 459 | const emailPayload: EmailPayload = { |
459 | template: 'verify-email', | 460 | template: 'verify-email', |
460 | to: [ to ], | 461 | to: [ to ], |
461 | subject: `Verify your email on ${WEBSERVER.HOST}`, | 462 | subject: `Verify your email on ${WEBSERVER.HOST}`, |
462 | locals: { | 463 | locals: { |
464 | username, | ||
463 | verifyEmailUrl | 465 | verifyEmailUrl |
464 | } | 466 | } |
465 | } | 467 | } |
diff --git a/server/lib/emails/password-reset/html.pug b/server/lib/emails/password-reset/html.pug index bb6a9d16b..ac33db5d7 100644 --- a/server/lib/emails/password-reset/html.pug +++ b/server/lib/emails/password-reset/html.pug | |||
@@ -5,8 +5,8 @@ block title | |||
5 | 5 | ||
6 | block content | 6 | block content |
7 | p. | 7 | p. |
8 | A reset password procedure for your account ${to} has been requested on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]. | 8 | A reset password procedure for your account #{username} has been requested on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]. |
9 | Please follow #[a(href=resetPasswordUrl) this link] to reset it: #[a(href=resetPasswordUrl) #{resetPasswordUrl}] | 9 | Please follow #[a(href=resetPasswordUrl) this link] to reset it: #[a(href=resetPasswordUrl) #{resetPasswordUrl}] |
10 | (the link will expire within 1 hour) | 10 | (the link will expire within 1 hour) |
11 | p. | 11 | p. |
12 | If you are not the person who initiated this request, please ignore this email. \ No newline at end of file | 12 | If you are not the person who initiated this request, please ignore this email. |
diff --git a/server/lib/user.ts b/server/lib/user.ts index 642549879..6e7a738ee 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -111,8 +111,9 @@ async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) { | |||
111 | if (isPendingEmail) url += '&isPendingEmail=true' | 111 | if (isPendingEmail) url += '&isPendingEmail=true' |
112 | 112 | ||
113 | const email = isPendingEmail ? user.pendingEmail : user.email | 113 | const email = isPendingEmail ? user.pendingEmail : user.email |
114 | const username = user.username | ||
114 | 115 | ||
115 | await Emailer.Instance.addVerifyEmailJob(email, url) | 116 | await Emailer.Instance.addVerifyEmailJob(username, email, url) |
116 | } | 117 | } |
117 | 118 | ||
118 | // --------------------------------------------------------------------------- | 119 | // --------------------------------------------------------------------------- |