diff options
Diffstat (limited to 'client')
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) |
4 | import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' | 4 | import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' |
5 | import { User, UserConstructorHash } from '../../shared/users/user.model' | 5 | import { User, UserConstructorHash } from '../../shared/users/user.model' |
6 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | ||
6 | 7 | ||
7 | export type TokenOptions = { | 8 | export 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' | |||
5 | import { ReplaySubject } from 'rxjs/ReplaySubject' | 5 | import { ReplaySubject } from 'rxjs/ReplaySubject' |
6 | import { ServerConfig } from '../../../../../shared' | 6 | import { ServerConfig } from '../../../../../shared' |
7 | import { About } from '../../../../../shared/models/server/about.model' | 7 | import { About } from '../../../../../shared/models/server/about.model' |
8 | import { ServerStats } from '../../../../../shared/models/server/server-stats.model' | ||
9 | import { environment } from '../../../environments/environment' | 8 | import { 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 @@ | |||
1 | import { Component, ElementRef, HostListener, Input, OnInit, ViewChild, OnChanges } from '@angular/core' | 1 | import { Component, Input, OnChanges, OnInit } from '@angular/core' |
2 | import { MarkdownService } from '@app/videos/shared' | 2 | import { MarkdownService } from '@app/videos/shared' |
3 | import { 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 | ||
11 | export class HelpComponent implements OnInit, OnChanges { | 10 | export 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 @@ | |||
1 | import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' | 1 | import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' |
2 | import { Account } from '../account/account.model' | 2 | import { Account } from '../account/account.model' |
3 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | ||
3 | 4 | ||
4 | export type UserConstructorHash = { | 5 | export 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 @@ | |||
1 | import { | 1 | import { UserRight, VideoChannel, VideoDetails as VideoDetailsServerModel, VideoFile } from '../../../../../shared' |
2 | UserRight, | ||
3 | VideoChannel, | ||
4 | VideoDetails as VideoDetailsServerModel, | ||
5 | VideoFile, | ||
6 | VideoPrivacy | ||
7 | } from '../../../../../shared' | ||
8 | import { Account } from '../../../../../shared/models/actors' | 2 | import { Account } from '../../../../../shared/models/actors' |
9 | import { VideoConstant } from '../../../../../shared/models/videos/video.model' | ||
10 | import { AuthUser } from '../../core' | 3 | import { AuthUser } from '../../core' |
11 | import { Video } from '../../shared/video/video.model' | 4 | import { Video } from '../../shared/video/video.model' |
12 | 5 | ||
13 | export class VideoDetails extends Video implements VideoDetailsServerModel { | 6 | export 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 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { User } from '../users' | 2 | import { User } from '../users' |
3 | import { Video } from './video.model' | 3 | import { Video } from './video.model' |
4 | import { 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 @@ | |||
1 | import { Account } from '@app/shared/account/account.model' | 1 | import { Account } from '@app/shared/account/account.model' |
2 | import { User } from '../' | 2 | import { User } from '../' |
3 | import { Video as VideoServerModel } from '../../../../../shared' | 3 | import { Video as VideoServerModel, VideoPrivacy } from '../../../../../shared' |
4 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 4 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' |
5 | import { VideoConstant } from '../../../../../shared/models/videos/video.model' | 5 | import { VideoConstant } from '../../../../../shared/models/videos/video.model' |
6 | import { getAbsoluteAPIUrl } from '../misc/utils' | 6 | import { getAbsoluteAPIUrl } from '../misc/utils' |
7 | import { ServerConfig } from '../../../../../shared/models' | ||
7 | 8 | ||
8 | export class Video implements VideoServerModel { | 9 | export 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' | |||
22 | import { VideoReportComponent } from './modal/video-report.component' | 22 | import { VideoReportComponent } from './modal/video-report.component' |
23 | import { VideoShareComponent } from './modal/video-share.component' | 23 | import { VideoShareComponent } from './modal/video-share.component' |
24 | import { getVideojsOptions } from '../../../assets/player/peertube-player' | 24 | import { getVideojsOptions } from '../../../assets/player/peertube-player' |
25 | import { 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 | |||
11 | body { | ||
12 | font-family: $main-fonts; | ||
13 | font-weight: $font-regular; | ||
14 | color: #000; | ||
15 | } | ||
16 | |||
7 | video { | 17 | video { |
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 | ||
12 | async function loadVideoInfo (videoId: string): Promise<VideoDetails> { | 12 | function loadVideoInfo (videoId: string): Promise<Response> { |
13 | const response = await fetch(getVideoUrl(videoId)) | 13 | return fetch(getVideoUrl(videoId)) |
14 | return response.json() | 14 | } |
15 | |||
16 | function removeElement (element: HTMLElement) { | ||
17 | element.parentElement.removeChild(element) | ||
18 | } | ||
19 | |||
20 | function 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 | |||
33 | function videoNotFound (videoElement: HTMLVideoElement) { | ||
34 | const text = 'This video does not exist.' | ||
35 | displayError(videoElement, text) | ||
36 | } | ||
37 | |||
38 | function videoFetchError (videoElement: HTMLVideoElement) { | ||
39 | const text = 'We cannot fetch the video. Please try again later.' | ||
40 | displayError(videoElement, text) | ||
15 | } | 41 | } |
16 | 42 | ||
17 | const urlParts = window.location.href.split('/') | 43 | const urlParts = window.location.href.split('/') |
18 | const videoId = urlParts[urlParts.length - 1] | 44 | const videoId = urlParts[urlParts.length - 1] |
19 | 45 | ||
20 | loadVideoInfo(videoId) | 46 | loadVideoInfo(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 | ||