aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html16
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts4
-rw-r--r--client/src/app/account/account-settings/account-details/account-details.component.html16
-rw-r--r--client/src/app/account/account-settings/account-details/account-details.component.scss6
-rw-r--r--client/src/app/account/account-settings/account-details/account-details.component.ts6
-rw-r--r--client/src/app/account/account-videos/account-videos.component.html1
-rw-r--r--client/src/app/account/account-videos/account-videos.component.scss6
-rw-r--r--client/src/app/core/auth/auth-user.model.ts9
-rw-r--r--client/src/app/core/server/server.service.ts2
-rw-r--r--client/src/app/shared/misc/help.component.html7
-rw-r--r--client/src/app/shared/misc/help.component.scss22
-rw-r--r--client/src/app/shared/misc/help.component.ts16
-rw-r--r--client/src/app/shared/users/user.model.ts9
-rw-r--r--client/src/app/shared/video/video-details.model.ts11
-rw-r--r--client/src/app/shared/video/video-miniature.component.html4
-rw-r--r--client/src/app/shared/video/video-miniature.component.ts7
-rw-r--r--client/src/app/shared/video/video.model.ts17
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html1
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.scss4
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts4
-rw-r--r--client/src/standalone/videos/embed.html6
-rw-r--r--client/src/standalone/videos/embed.scss45
-rw-r--r--client/src/standalone/videos/embed.ts44
23 files changed, 191 insertions, 72 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index 714a3af15..df40bba9f 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -62,6 +62,22 @@
62 </div> 62 </div>
63 </div> 63 </div>
64 64
65 <div class="form-group">
66 <label for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
67 <my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
68
69 <div class="peertube-select-container">
70 <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy">
71 <option value="do_not_list">Do not list</option>
72 <option value="blur">Blur thumbnails</option>
73 <option value="display">Display</option>
74 </select>
75 </div>
76 <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error">
77 {{ formErrors.instanceDefaultNSFWPolicy }}
78 </div>
79 </div>
80
65 <div class="inner-form-title">Cache</div> 81 <div class="inner-form-title">Cache</div>
66 82
67 <div class="form-group"> 83 <div class="form-group">
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index d73ee71e4..2ab371cbb 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -48,6 +48,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
48 instanceDescription: '', 48 instanceDescription: '',
49 instanceTerms: '', 49 instanceTerms: '',
50 instanceDefaultClientRoute: '', 50 instanceDefaultClientRoute: '',
51 instanceDefaultNSFWPolicy: '',
51 cachePreviewsSize: '', 52 cachePreviewsSize: '',
52 signupLimit: '', 53 signupLimit: '',
53 adminEmail: '', 54 adminEmail: '',
@@ -90,6 +91,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
90 instanceDescription: [ '' ], 91 instanceDescription: [ '' ],
91 instanceTerms: [ '' ], 92 instanceTerms: [ '' ],
92 instanceDefaultClientRoute: [ '' ], 93 instanceDefaultClientRoute: [ '' ],
94 instanceDefaultNSFWPolicy: [ '' ],
93 cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ], 95 cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ],
94 signupEnabled: [ ], 96 signupEnabled: [ ],
95 signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ], 97 signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ],
@@ -167,6 +169,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
167 description: this.form.value['instanceDescription'], 169 description: this.form.value['instanceDescription'],
168 terms: this.form.value['instanceTerms'], 170 terms: this.form.value['instanceTerms'],
169 defaultClientRoute: this.form.value['instanceDefaultClientRoute'], 171 defaultClientRoute: this.form.value['instanceDefaultClientRoute'],
172 defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'],
170 customizations: { 173 customizations: {
171 javascript: this.form.value['customizationJavascript'], 174 javascript: this.form.value['customizationJavascript'],
172 css: this.form.value['customizationCSS'] 175 css: this.form.value['customizationCSS']
@@ -224,6 +227,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
224 instanceDescription: this.customConfig.instance.description, 227 instanceDescription: this.customConfig.instance.description,
225 instanceTerms: this.customConfig.instance.terms, 228 instanceTerms: this.customConfig.instance.terms,
226 instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute, 229 instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute,
230 instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy,
227 cachePreviewsSize: this.customConfig.cache.previews.size, 231 cachePreviewsSize: this.customConfig.cache.previews.size,
228 signupEnabled: this.customConfig.signup.enabled, 232 signupEnabled: this.customConfig.signup.enabled,
229 signupLimit: this.customConfig.signup.limit, 233 signupLimit: this.customConfig.signup.limit,
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.html b/client/src/app/account/account-settings/account-details/account-details.component.html
index 8f1475a4d..0e8598e9e 100644
--- a/client/src/app/account/account-settings/account-details/account-details.component.html
+++ b/client/src/app/account/account-settings/account-details/account-details.component.html
@@ -1,11 +1,15 @@
1<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> 1<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
2 <div class="form-group"> 2 <div class="form-group">
3 <input 3 <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
4 type="checkbox" id="displayNSFW" 4 <my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
5 formControlName="displayNSFW" 5
6 > 6 <div class="peertube-select-container">
7 <label for="displayNSFW"></label> 7 <select id="nsfwPolicy" formControlName="nsfwPolicy">
8 <label for="displayNSFW">Display videos that contain mature or explicit content</label> 8 <option value="do_not_list">Do not list</option>
9 <option value="blur">Blur thumbnails</option>
10 <option value="display">Display</option>
11 </select>
12 </div>
9 </div> 13 </div>
10 14
11 <div class="form-group"> 15 <div class="form-group">
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.scss b/client/src/app/account/account-settings/account-details/account-details.component.scss
index 4e8dfde1d..ed59e4689 100644
--- a/client/src/app/account/account-settings/account-details/account-details.component.scss
+++ b/client/src/app/account/account-settings/account-details/account-details.component.scss
@@ -12,3 +12,9 @@ input[type=submit] {
12 display: block; 12 display: block;
13 margin-top: 15px; 13 margin-top: 15px;
14} 14}
15
16.peertube-select-container {
17 @include peertube-select-container(340px);
18
19 margin-bottom: 30px;
20} \ No newline at end of file
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.ts b/client/src/app/account/account-settings/account-details/account-details.component.ts
index 917f31651..de213717e 100644
--- a/client/src/app/account/account-settings/account-details/account-details.component.ts
+++ b/client/src/app/account/account-settings/account-details/account-details.component.ts
@@ -29,7 +29,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
29 29
30 buildForm () { 30 buildForm () {
31 this.form = this.formBuilder.group({ 31 this.form = this.formBuilder.group({
32 displayNSFW: [ this.user.displayNSFW ], 32 nsfwPolicy: [ this.user.nsfwPolicy ],
33 autoPlayVideo: [ this.user.autoPlayVideo ] 33 autoPlayVideo: [ this.user.autoPlayVideo ]
34 }) 34 })
35 35
@@ -41,10 +41,10 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
41 } 41 }
42 42
43 updateDetails () { 43 updateDetails () {
44 const displayNSFW = this.form.value['displayNSFW'] 44 const nsfwPolicy = this.form.value['nsfwPolicy']
45 const autoPlayVideo = this.form.value['autoPlayVideo'] 45 const autoPlayVideo = this.form.value['autoPlayVideo']
46 const details: UserUpdateMe = { 46 const details: UserUpdateMe = {
47 displayNSFW, 47 nsfwPolicy,
48 autoPlayVideo 48 autoPlayVideo
49 } 49 }
50 50
diff --git a/client/src/app/account/account-videos/account-videos.component.html b/client/src/app/account/account-videos/account-videos.component.html
index d7e2230b0..66ce3a77b 100644
--- a/client/src/app/account/account-videos/account-videos.component.html
+++ b/client/src/app/account/account-videos/account-videos.component.html
@@ -18,6 +18,7 @@
18 <div class="video-info"> 18 <div class="video-info">
19 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> 19 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
20 <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> 20 <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
21 <div class="video-info-private">{{ video.privacy.label }}</div>
21 </div> 22 </div>
22 23
23 <!-- Display only once --> 24 <!-- Display only once -->
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index 449cc6af4..f276ea389 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -79,8 +79,12 @@
79 font-weight: $font-semibold; 79 font-weight: $font-semibold;
80 } 80 }
81 81
82 .video-info-date-views { 82 .video-info-date-views, .video-info-private {
83 font-size: 13px; 83 font-size: 13px;
84
85 &.video-info-private {
86 font-weight: $font-semibold;
87 }
84 } 88 }
85 } 89 }
86 90
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts
index 366eea110..74ed1c580 100644
--- a/client/src/app/core/auth/auth-user.model.ts
+++ b/client/src/app/core/auth/auth-user.model.ts
@@ -3,6 +3,7 @@ import { UserRight } from '../../../../../shared/models/users/user-right.enum'
3// Do not use the barrel (dependency loop) 3// Do not use the barrel (dependency loop)
4import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' 4import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
5import { User, UserConstructorHash } from '../../shared/users/user.model' 5import { User, UserConstructorHash } from '../../shared/users/user.model'
6import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
6 7
7export type TokenOptions = { 8export type TokenOptions = {
8 accessToken: string 9 accessToken: string
@@ -70,7 +71,7 @@ export class AuthUser extends User {
70 ROLE: 'role', 71 ROLE: 'role',
71 EMAIL: 'email', 72 EMAIL: 'email',
72 USERNAME: 'username', 73 USERNAME: 'username',
73 DISPLAY_NSFW: 'display_nsfw', 74 NSFW_POLICY: 'nsfw_policy',
74 AUTO_PLAY_VIDEO: 'auto_play_video' 75 AUTO_PLAY_VIDEO: 'auto_play_video'
75 } 76 }
76 77
@@ -85,7 +86,7 @@ export class AuthUser extends User {
85 username: peertubeLocalStorage.getItem(this.KEYS.USERNAME), 86 username: peertubeLocalStorage.getItem(this.KEYS.USERNAME),
86 email: peertubeLocalStorage.getItem(this.KEYS.EMAIL), 87 email: peertubeLocalStorage.getItem(this.KEYS.EMAIL),
87 role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole, 88 role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole,
88 displayNSFW: peertubeLocalStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true', 89 nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType,
89 autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true' 90 autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true'
90 }, 91 },
91 Tokens.load() 92 Tokens.load()
@@ -99,7 +100,7 @@ export class AuthUser extends User {
99 peertubeLocalStorage.removeItem(this.KEYS.USERNAME) 100 peertubeLocalStorage.removeItem(this.KEYS.USERNAME)
100 peertubeLocalStorage.removeItem(this.KEYS.ID) 101 peertubeLocalStorage.removeItem(this.KEYS.ID)
101 peertubeLocalStorage.removeItem(this.KEYS.ROLE) 102 peertubeLocalStorage.removeItem(this.KEYS.ROLE)
102 peertubeLocalStorage.removeItem(this.KEYS.DISPLAY_NSFW) 103 peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY)
103 peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO) 104 peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO)
104 peertubeLocalStorage.removeItem(this.KEYS.EMAIL) 105 peertubeLocalStorage.removeItem(this.KEYS.EMAIL)
105 Tokens.flush() 106 Tokens.flush()
@@ -136,7 +137,7 @@ export class AuthUser extends User {
136 peertubeLocalStorage.setItem(AuthUser.KEYS.USERNAME, this.username) 137 peertubeLocalStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
137 peertubeLocalStorage.setItem(AuthUser.KEYS.EMAIL, this.email) 138 peertubeLocalStorage.setItem(AuthUser.KEYS.EMAIL, this.email)
138 peertubeLocalStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString()) 139 peertubeLocalStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString())
139 peertubeLocalStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW)) 140 peertubeLocalStorage.setItem(AuthUser.KEYS.NSFW_POLICY, this.nsfwPolicy.toString())
140 peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo)) 141 peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo))
141 this.tokens.save() 142 this.tokens.save()
142 } 143 }
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 987d64d2a..a8beb242d 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -5,7 +5,6 @@ import 'rxjs/add/operator/do'
5import { ReplaySubject } from 'rxjs/ReplaySubject' 5import { ReplaySubject } from 'rxjs/ReplaySubject'
6import { ServerConfig } from '../../../../../shared' 6import { ServerConfig } from '../../../../../shared'
7import { About } from '../../../../../shared/models/server/about.model' 7import { About } from '../../../../../shared/models/server/about.model'
8import { ServerStats } from '../../../../../shared/models/server/server-stats.model'
9import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
10 9
11@Injectable() 10@Injectable()
@@ -26,6 +25,7 @@ export class ServerService {
26 shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' + 25 shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' +
27 'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.', 26 'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.',
28 defaultClientRoute: '', 27 defaultClientRoute: '',
28 defaultNSFWPolicy: 'do_not_list' as 'do_not_list',
29 customizations: { 29 customizations: {
30 javascript: '', 30 javascript: '',
31 css: '' 31 css: ''
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html
index e37d93b62..3da5701a0 100644
--- a/client/src/app/shared/misc/help.component.html
+++ b/client/src/app/shared/misc/help.component.html
@@ -13,6 +13,9 @@
13</ng-template> 13</ng-template>
14 14
15<span 15<span
16 class="help-tooltip-button" containerClass="help-tooltip" title="Click to get help" 16 class="help-tooltip-button"
17 #tooltipDirective="bs-tooltip" [tooltip]="tooltipTemplate" triggers="click" 17 title="Get help"
18 [popover]="tooltipTemplate"
19 [placement]="tooltipPlacement"
20 [outsideClick]="true"
18></span> 21></span>
diff --git a/client/src/app/shared/misc/help.component.scss b/client/src/app/shared/misc/help.component.scss
index b8bf3a7a5..0df8b86fa 100644
--- a/client/src/app/shared/misc/help.component.scss
+++ b/client/src/app/shared/misc/help.component.scss
@@ -12,20 +12,16 @@
12} 12}
13 13
14/deep/ { 14/deep/ {
15 .help-tooltip { 15 .popover-body {
16 opacity: 1 !important; 16 text-align: left;
17 padding: 10px;
18 max-width: 300px;
17 19
18 .tooltip-inner { 20 font-size: 13px;
19 text-align: left; 21 font-family: $main-fonts;
20 padding: 10px; 22 background-color: #fff;
21 max-width: 300px; 23 color: #000;
22 24 box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
23 font-size: 13px;
24 font-family: $main-fonts;
25 background-color: #fff;
26 color: #000;
27 box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
28 }
29 25
30 ul { 26 ul {
31 padding-left: 20px; 27 padding-left: 20px;
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/misc/help.component.ts
index 89dd1dae5..0373a63de 100644
--- a/client/src/app/shared/misc/help.component.ts
+++ b/client/src/app/shared/misc/help.component.ts
@@ -1,6 +1,5 @@
1import { Component, ElementRef, HostListener, Input, OnInit, ViewChild, OnChanges } from '@angular/core' 1import { Component, Input, OnChanges, OnInit } from '@angular/core'
2import { MarkdownService } from '@app/videos/shared' 2import { MarkdownService } from '@app/videos/shared'
3import { TooltipDirective } from 'ngx-bootstrap/tooltip'
4 3
5@Component({ 4@Component({
6 selector: 'my-help', 5 selector: 'my-help',
@@ -9,16 +8,14 @@ import { TooltipDirective } from 'ngx-bootstrap/tooltip'
9}) 8})
10 9
11export class HelpComponent implements OnInit, OnChanges { 10export class HelpComponent implements OnInit, OnChanges {
12 @ViewChild('tooltipDirective') tooltipDirective: TooltipDirective
13 @Input() preHtml = '' 11 @Input() preHtml = ''
14 @Input() postHtml = '' 12 @Input() postHtml = ''
15 @Input() customHtml = '' 13 @Input() customHtml = ''
16 @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' 14 @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom'
15 @Input() tooltipPlacement = 'right'
17 16
18 mainHtml = '' 17 mainHtml = ''
19 18
20 constructor (private elementRef: ElementRef) { }
21
22 ngOnInit () { 19 ngOnInit () {
23 this.init() 20 this.init()
24 } 21 }
@@ -27,15 +24,6 @@ export class HelpComponent implements OnInit, OnChanges {
27 this.init() 24 this.init()
28 } 25 }
29 26
30 @HostListener('document:click', ['$event.target'])
31 public onClick (targetElement) {
32 const clickedInside = this.elementRef.nativeElement.contains(targetElement)
33
34 if (this.tooltipDirective.isOpen && !clickedInside) {
35 this.tooltipDirective.hide()
36 }
37 }
38
39 private init () { 27 private init () {
40 if (this.helpType === 'custom') { 28 if (this.helpType === 'custom') {
41 this.mainHtml = this.customHtml 29 this.mainHtml = this.customHtml
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 4a94b032d..2bdc48a1d 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,5 +1,6 @@
1import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' 1import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared'
2import { Account } from '../account/account.model' 2import { Account } from '../account/account.model'
3import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
3 4
4export type UserConstructorHash = { 5export type UserConstructorHash = {
5 id: number, 6 id: number,
@@ -7,7 +8,7 @@ export type UserConstructorHash = {
7 email: string, 8 email: string,
8 role: UserRole, 9 role: UserRole,
9 videoQuota?: number, 10 videoQuota?: number,
10 displayNSFW?: boolean, 11 nsfwPolicy?: NSFWPolicyType,
11 autoPlayVideo?: boolean, 12 autoPlayVideo?: boolean,
12 createdAt?: Date, 13 createdAt?: Date,
13 account?: Account, 14 account?: Account,
@@ -18,7 +19,7 @@ export class User implements UserServerModel {
18 username: string 19 username: string
19 email: string 20 email: string
20 role: UserRole 21 role: UserRole
21 displayNSFW: boolean 22 nsfwPolicy: NSFWPolicyType
22 autoPlayVideo: boolean 23 autoPlayVideo: boolean
23 videoQuota: number 24 videoQuota: number
24 account: Account 25 account: Account
@@ -40,8 +41,8 @@ export class User implements UserServerModel {
40 this.videoQuota = hash.videoQuota 41 this.videoQuota = hash.videoQuota
41 } 42 }
42 43
43 if (hash.displayNSFW !== undefined) { 44 if (hash.nsfwPolicy !== undefined) {
44 this.displayNSFW = hash.displayNSFW 45 this.nsfwPolicy = hash.nsfwPolicy
45 } 46 }
46 47
47 if (hash.autoPlayVideo !== undefined) { 48 if (hash.autoPlayVideo !== undefined) {
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index a1f7207a2..5397aa37f 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -1,17 +1,9 @@
1import { 1import { UserRight, VideoChannel, VideoDetails as VideoDetailsServerModel, VideoFile } from '../../../../../shared'
2 UserRight,
3 VideoChannel,
4 VideoDetails as VideoDetailsServerModel,
5 VideoFile,
6 VideoPrivacy
7} from '../../../../../shared'
8import { Account } from '../../../../../shared/models/actors' 2import { Account } from '../../../../../shared/models/actors'
9import { VideoConstant } from '../../../../../shared/models/videos/video.model'
10import { AuthUser } from '../../core' 3import { AuthUser } from '../../core'
11import { Video } from '../../shared/video/video.model' 4import { Video } from '../../shared/video/video.model'
12 5
13export class VideoDetails extends Video implements VideoDetailsServerModel { 6export class VideoDetails extends Video implements VideoDetailsServerModel {
14 privacy: VideoConstant<VideoPrivacy>
15 descriptionPath: string 7 descriptionPath: string
16 support: string 8 support: string
17 channel: VideoChannel 9 channel: VideoChannel
@@ -26,7 +18,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
26 constructor (hash: VideoDetailsServerModel) { 18 constructor (hash: VideoDetailsServerModel) {
27 super(hash) 19 super(hash)
28 20
29 this.privacy = hash.privacy
30 this.descriptionPath = hash.descriptionPath 21 this.descriptionPath = hash.descriptionPath
31 this.files = hash.files 22 this.files = hash.files
32 this.channel = hash.channel 23 this.channel = hash.channel
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html
index f28e9b8d9..233432142 100644
--- a/client/src/app/shared/video/video-miniature.component.html
+++ b/client/src/app/shared/video/video-miniature.component.html
@@ -1,11 +1,11 @@
1<div class="video-miniature"> 1<div class="video-miniature">
2 <my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail> 2 <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur()"></my-video-thumbnail>
3 3
4 <div class="video-miniature-information"> 4 <div class="video-miniature-information">
5 <span class="video-miniature-name"> 5 <span class="video-miniature-name">
6 <a 6 <a
7 class="video-miniature-name" 7 class="video-miniature-name"
8 [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }" 8 [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }"
9 > 9 >
10 {{ video.name }} 10 {{ video.name }}
11 </a> 11 </a>
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts
index 4d79a74bb..d3f6dc1f6 100644
--- a/client/src/app/shared/video/video-miniature.component.ts
+++ b/client/src/app/shared/video/video-miniature.component.ts
@@ -1,6 +1,7 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { User } from '../users' 2import { User } from '../users'
3import { Video } from './video.model' 3import { Video } from './video.model'
4import { ServerService } from '@app/core'
4 5
5@Component({ 6@Component({
6 selector: 'my-video-miniature', 7 selector: 'my-video-miniature',
@@ -11,7 +12,9 @@ export class VideoMiniatureComponent {
11 @Input() user: User 12 @Input() user: User
12 @Input() video: Video 13 @Input() video: Video
13 14
14 isVideoNSFWForThisUser () { 15 constructor (private serverService: ServerService) { }
15 return this.video.isVideoNSFWForUser(this.user) 16
17 isVideoBlur () {
18 return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
16 } 19 }
17} 20}
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 0c02cbcb9..e25c172e0 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -1,9 +1,10 @@
1import { Account } from '@app/shared/account/account.model' 1import { Account } from '@app/shared/account/account.model'
2import { User } from '../' 2import { User } from '../'
3import { Video as VideoServerModel } from '../../../../../shared' 3import { Video as VideoServerModel, VideoPrivacy } from '../../../../../shared'
4import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 4import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
5import { VideoConstant } from '../../../../../shared/models/videos/video.model' 5import { VideoConstant } from '../../../../../shared/models/videos/video.model'
6import { getAbsoluteAPIUrl } from '../misc/utils' 6import { getAbsoluteAPIUrl } from '../misc/utils'
7import { ServerConfig } from '../../../../../shared/models'
7 8
8export class Video implements VideoServerModel { 9export class Video implements VideoServerModel {
9 by: string 10 by: string
@@ -13,6 +14,7 @@ export class Video implements VideoServerModel {
13 category: VideoConstant<number> 14 category: VideoConstant<number>
14 licence: VideoConstant<number> 15 licence: VideoConstant<number>
15 language: VideoConstant<number> 16 language: VideoConstant<number>
17 privacy: VideoConstant<VideoPrivacy>
16 description: string 18 description: string
17 duration: number 19 duration: number
18 durationLabel: string 20 durationLabel: string
@@ -61,6 +63,7 @@ export class Video implements VideoServerModel {
61 this.category = hash.category 63 this.category = hash.category
62 this.licence = hash.licence 64 this.licence = hash.licence
63 this.language = hash.language 65 this.language = hash.language
66 this.privacy = hash.privacy
64 this.description = hash.description 67 this.description = hash.description
65 this.duration = hash.duration 68 this.duration = hash.duration
66 this.durationLabel = Video.createDurationString(hash.duration) 69 this.durationLabel = Video.createDurationString(hash.duration)
@@ -83,8 +86,14 @@ export class Video implements VideoServerModel {
83 this.by = Account.CREATE_BY_STRING(hash.account.name, hash.account.host) 86 this.by = Account.CREATE_BY_STRING(hash.account.name, hash.account.host)
84 } 87 }
85 88
86 isVideoNSFWForUser (user: User) { 89 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
87 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos... 90 // Video is not NSFW, skip
88 return (this.nsfw && (!user || user.displayNSFW === false)) 91 if (this.nsfw === false) return false
92
93 // Return user setting if logged in
94 if (user) return user.nsfwPolicy !== 'display'
95
96 // Return default instance config
97 return serverConfig.instance.defaultNSFWPolicy !== 'display'
89 } 98 }
90} 99}
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index 6d0a1600a..9cd3454a0 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -100,6 +100,7 @@
100 <input type="checkbox" id="nsfw" formControlName="nsfw" /> 100 <input type="checkbox" id="nsfw" formControlName="nsfw" />
101 <label for="nsfw"></label> 101 <label for="nsfw"></label>
102 <label for="nsfw">This video contains mature or explicit content</label> 102 <label for="nsfw">This video contains mature or explicit content</label>
103 <my-help tooltipPlacement="top" helpType="custom" customHtml="Some instances do not list NSFW videos by default."></my-help>
103 </div> 104 </div>
104 105
105 <div class="form-group form-group-checkbox"> 106 <div class="form-group form-group-checkbox">
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
index 1317f7426..cf64ff589 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
@@ -9,6 +9,10 @@
9 @include peertube-select-disabled-container(auto); 9 @include peertube-select-disabled-container(auto);
10} 10}
11 11
12.form-group-checkbox {
13 my-help { margin-left: 5px }
14}
15
12.video-edit { 16.video-edit {
13 height: 100%; 17 height: 100%;
14 18
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 182703cdf..6f6f02378 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -22,6 +22,7 @@ import { VideoDownloadComponent } from './modal/video-download.component'
22import { VideoReportComponent } from './modal/video-report.component' 22import { VideoReportComponent } from './modal/video-report.component'
23import { VideoShareComponent } from './modal/video-share.component' 23import { VideoShareComponent } from './modal/video-share.component'
24import { getVideojsOptions } from '../../../assets/player/peertube-player' 24import { getVideojsOptions } from '../../../assets/player/peertube-player'
25import { ServerService } from '@app/core'
25 26
26@Component({ 27@Component({
27 selector: 'my-video-watch', 28 selector: 'my-video-watch',
@@ -66,6 +67,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
66 private confirmService: ConfirmService, 67 private confirmService: ConfirmService,
67 private metaService: MetaService, 68 private metaService: MetaService,
68 private authService: AuthService, 69 private authService: AuthService,
70 private serverService: ServerService,
69 private notificationsService: NotificationsService, 71 private notificationsService: NotificationsService,
70 private markdownService: MarkdownService, 72 private markdownService: MarkdownService,
71 private zone: NgZone, 73 private zone: NgZone,
@@ -335,7 +337,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
335 337
336 this.updateOtherVideosDisplayed() 338 this.updateOtherVideosDisplayed()
337 339
338 if (this.video.isVideoNSFWForUser(this.user)) { 340 if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
339 const res = await this.confirmService.confirm( 341 const res = await this.confirmService.confirm(
340 'This video contains mature or explicit content. Are you sure you want to watch it?', 342 'This video contains mature or explicit content. Are you sure you want to watch it?',
341 'Mature or explicit content' 343 'Mature or explicit content'
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html
index a359255da..b60b03a22 100644
--- a/client/src/standalone/videos/embed.html
+++ b/client/src/standalone/videos/embed.html
@@ -11,6 +11,12 @@
11 11
12 <body> 12 <body>
13 13
14 <div id="error-block">
15 <h1 id="error-title">Sorry</h1>
16
17 <div id="error-content"></div>
18 </div>
19
14 <video id="video-container" class="video-js vjs-peertube-skin"> 20 <video id="video-container" class="video-js vjs-peertube-skin">
15 </video> 21 </video>
16 22
diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss
index fc7135d64..37c1df6c4 100644
--- a/client/src/standalone/videos/embed.scss
+++ b/client/src/standalone/videos/embed.scss
@@ -4,6 +4,16 @@
4@import '~videojs-dock/dist/videojs-dock.css'; 4@import '~videojs-dock/dist/videojs-dock.css';
5@import '../../sass/video-js-custom.scss'; 5@import '../../sass/video-js-custom.scss';
6 6
7[hidden] {
8 display: none !important;
9}
10
11body {
12 font-family: $main-fonts;
13 font-weight: $font-regular;
14 color: #000;
15}
16
7video { 17video {
8 width: 99%; 18 width: 99%;
9} 19}
@@ -43,3 +53,38 @@ html, body {
43 } 53 }
44 } 54 }
45} 55}
56
57#error-block {
58 display: none;
59
60 flex-direction: column;
61 align-content: center;
62 justify-content: center;
63 text-align: center;
64 background-color: #141313;
65 width: 100%;
66 height: 100%;
67 color: white;
68 box-sizing: border-box;
69 font-family: sans-serif;
70
71 #error-title {
72 font-size: 45px;
73 margin-bottom: 5px;
74 }
75
76 #error-content {
77 font-size: 24px;
78 }
79}
80
81@media screen and (max-width: 300px) {
82 #error-block {
83 font-size: 36px;
84
85 #error-content {
86 font-size: 14px;
87 }
88 }
89}
90
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index a99bc586f..aa418d2d4 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -9,19 +9,53 @@ function getVideoUrl (id: string) {
9 return window.location.origin + '/api/v1/videos/' + id 9 return window.location.origin + '/api/v1/videos/' + id
10} 10}
11 11
12async function loadVideoInfo (videoId: string): Promise<VideoDetails> { 12function loadVideoInfo (videoId: string): Promise<Response> {
13 const response = await fetch(getVideoUrl(videoId)) 13 return fetch(getVideoUrl(videoId))
14 return response.json() 14}
15
16function removeElement (element: HTMLElement) {
17 element.parentElement.removeChild(element)
18}
19
20function displayError (videoElement: HTMLVideoElement, text: string) {
21 // Remove video element
22 removeElement(videoElement)
23
24 document.title = 'Sorry - ' + text
25
26 const errorBlock = document.getElementById('error-block')
27 errorBlock.style.display = 'flex'
28
29 const errorText = document.getElementById('error-content')
30 errorText.innerHTML = text
31}
32
33function videoNotFound (videoElement: HTMLVideoElement) {
34 const text = 'This video does not exist.'
35 displayError(videoElement, text)
36}
37
38function videoFetchError (videoElement: HTMLVideoElement) {
39 const text = 'We cannot fetch the video. Please try again later.'
40 displayError(videoElement, text)
15} 41}
16 42
17const urlParts = window.location.href.split('/') 43const urlParts = window.location.href.split('/')
18const videoId = urlParts[urlParts.length - 1] 44const videoId = urlParts[urlParts.length - 1]
19 45
20loadVideoInfo(videoId) 46loadVideoInfo(videoId)
21 .then(videoInfo => { 47 .then(async response => {
22 const videoContainerId = 'video-container' 48 const videoContainerId = 'video-container'
23
24 const videoElement = document.getElementById(videoContainerId) as HTMLVideoElement 49 const videoElement = document.getElementById(videoContainerId) as HTMLVideoElement
50
51 if (!response.ok) {
52 if (response.status === 404) return videoNotFound(videoElement)
53
54 return videoFetchError(videoElement)
55 }
56
57 const videoInfo: VideoDetails = await response.json()
58
25 let autoplay = false 59 let autoplay = false
26 let startTime = 0 60 let startTime = 0
27 61