aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html4
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.component.html2
-rw-r--r--client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html2
-rw-r--r--client/src/app/+search/channel-lazy-load.resolver.ts8
-rw-r--r--client/src/app/+search/search.component.html20
-rw-r--r--client/src/app/+search/search.component.ts36
-rw-r--r--client/src/app/+search/video-lazy-load.resolver.ts8
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.scss2
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.html2
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.html2
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.html15
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts7
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.html12
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.ts25
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.scss9
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html2
-rw-r--r--client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts54
-rw-r--r--client/src/sass/primeng-custom.scss20
-rw-r--r--server/controllers/api/users/index.ts2
-rw-r--r--server/lib/emailer.ts6
-rw-r--r--server/lib/emails/password-reset/html.pug6
-rw-r--r--server/lib/user.ts3
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
5import { immutableAssign } from '@app/helpers' 5import { immutableAssign } from '@app/helpers'
6import { Video, VideoChannel } from '@app/shared/shared-main' 6import { Video, VideoChannel } from '@app/shared/shared-main'
7import { AdvancedSearch, SearchService } from '@app/shared/shared-search' 7import { AdvancedSearch, SearchService } from '@app/shared/shared-search'
8import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' 8import { MiniatureDisplayOptions, VideoLinkType } from '@app/shared/shared-video-miniature'
9import { MetaService } from '@ngx-meta/core' 9import { MetaService } from '@ngx-meta/core'
10import { I18n } from '@ngx-translate/i18n-polyfill' 10import { I18n } from '@ngx-translate/i18n-polyfill'
11import { SearchTargetType, ServerConfig } from '@shared/models' 11import { 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'
11export class VideoThumbnailComponent { 11export 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}
32export 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
6block content 6block 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// ---------------------------------------------------------------------------