diff options
Diffstat (limited to 'client/src')
28 files changed, 287 insertions, 137 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index 1026c4e0d..7f2a6aa77 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -116,95 +116,99 @@ | |||
116 | <my-custom-markup-container [content]="descriptionContent"></my-custom-markup-container> | 116 | <my-custom-markup-container [content]="descriptionContent"></my-custom-markup-container> |
117 | </div> | 117 | </div> |
118 | 118 | ||
119 | <div class="anchor" id="moderation"></div> | 119 | <div myPluginSelector pluginSelectorId="about-instance-moderation"> |
120 | <a | 120 | <div class="anchor" id="moderation"></div> |
121 | *ngIf="html.moderationInformation || html.codeOfConduct || html.terms" | ||
122 | class="anchor-link" | ||
123 | routerLink="/about/instance" | ||
124 | fragment="moderation" | ||
125 | #anchorLink | ||
126 | (click)="onClickCopyLink(anchorLink)"> | ||
127 | <h2 i18n class="middle-title"> | ||
128 | MODERATION | ||
129 | </h2> | ||
130 | </a> | ||
131 | |||
132 | <div class="block moderation-information" *ngIf="html.moderationInformation"> | ||
133 | <div class="anchor" id="moderation-information"></div> | ||
134 | <a | 121 | <a |
122 | *ngIf="html.moderationInformation || html.codeOfConduct || html.terms" | ||
135 | class="anchor-link" | 123 | class="anchor-link" |
136 | routerLink="/about/instance" | 124 | routerLink="/about/instance" |
137 | fragment="moderation-information" | 125 | fragment="moderation" |
138 | #anchorLink | 126 | #anchorLink |
139 | (click)="onClickCopyLink(anchorLink)"> | 127 | (click)="onClickCopyLink(anchorLink)"> |
140 | <h3 i18n class="section-title">Moderation information</h3> | 128 | <h2 i18n class="middle-title"> |
129 | MODERATION | ||
130 | </h2> | ||
141 | </a> | 131 | </a> |
142 | 132 | ||
143 | <div [innerHTML]="html.moderationInformation"></div> | 133 | <div class="block moderation-information" *ngIf="html.moderationInformation"> |
144 | </div> | 134 | <div class="anchor" id="moderation-information"></div> |
135 | <a | ||
136 | class="anchor-link" | ||
137 | routerLink="/about/instance" | ||
138 | fragment="moderation-information" | ||
139 | #anchorLink | ||
140 | (click)="onClickCopyLink(anchorLink)"> | ||
141 | <h3 i18n class="section-title">Moderation information</h3> | ||
142 | </a> | ||
145 | 143 | ||
146 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> | 144 | <div [innerHTML]="html.moderationInformation"></div> |
147 | <div class="anchor" id="code-of-conduct"></div> | 145 | </div> |
148 | <a | ||
149 | class="anchor-link" | ||
150 | routerLink="/about/instance" | ||
151 | fragment="code-of-conduct" | ||
152 | #anchorLink | ||
153 | (click)="onClickCopyLink(anchorLink)"> | ||
154 | <h3 i18n class="section-title">Code of conduct</h3> | ||
155 | </a> | ||
156 | 146 | ||
157 | <div [innerHTML]="html.codeOfConduct"></div> | 147 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> |
158 | </div> | 148 | <div class="anchor" id="code-of-conduct"></div> |
149 | <a | ||
150 | class="anchor-link" | ||
151 | routerLink="/about/instance" | ||
152 | fragment="code-of-conduct" | ||
153 | #anchorLink | ||
154 | (click)="onClickCopyLink(anchorLink)"> | ||
155 | <h3 i18n class="section-title">Code of conduct</h3> | ||
156 | </a> | ||
159 | 157 | ||
160 | <div class="block terms"> | 158 | <div [innerHTML]="html.codeOfConduct"></div> |
161 | <div class="anchor" id="terms"></div> | 159 | </div> |
162 | <a | ||
163 | class="anchor-link" | ||
164 | routerLink="/about/instance" | ||
165 | fragment="terms" | ||
166 | #anchorLink | ||
167 | (click)="onClickCopyLink(anchorLink)"> | ||
168 | <h3 i18n class="section-title">Terms</h3> | ||
169 | </a> | ||
170 | 160 | ||
171 | <div [innerHTML]="html.terms"></div> | 161 | <div class="block terms"> |
172 | </div> | 162 | <div class="anchor" id="terms"></div> |
163 | <a | ||
164 | class="anchor-link" | ||
165 | routerLink="/about/instance" | ||
166 | fragment="terms" | ||
167 | #anchorLink | ||
168 | (click)="onClickCopyLink(anchorLink)"> | ||
169 | <h3 i18n class="section-title">Terms</h3> | ||
170 | </a> | ||
173 | 171 | ||
174 | <div class="anchor" id="other-information"></div> | 172 | <div [innerHTML]="html.terms"></div> |
175 | <a | 173 | </div> |
176 | *ngIf="html.hardwareInformation" | 174 | </div> |
177 | class="anchor-link" | ||
178 | routerLink="/about/instance" | ||
179 | fragment="other-information" | ||
180 | #anchorLink | ||
181 | (click)="onClickCopyLink(anchorLink)"> | ||
182 | <h2 i18n class="middle-title"> | ||
183 | OTHER INFORMATION | ||
184 | </h2> | ||
185 | </a> | ||
186 | 175 | ||
187 | <div class="block hardware-information" *ngIf="html.hardwareInformation"> | 176 | <div myPluginSelector pluginSelectorId="about-instance-other-information"> |
188 | <div class="anchor" id="hardware-information"></div> | 177 | <div class="anchor" id="other-information"></div> |
189 | <a | 178 | <a |
179 | *ngIf="html.hardwareInformation" | ||
190 | class="anchor-link" | 180 | class="anchor-link" |
191 | routerLink="/about/instance" | 181 | routerLink="/about/instance" |
192 | fragment="hardware-information" | 182 | fragment="other-information" |
193 | #anchorLink | 183 | #anchorLink |
194 | (click)="onClickCopyLink(anchorLink)"> | 184 | (click)="onClickCopyLink(anchorLink)"> |
195 | <h3 i18n class="section-title">Hardware information</h3> | 185 | <h2 i18n class="middle-title"> |
186 | OTHER INFORMATION | ||
187 | </h2> | ||
196 | </a> | 188 | </a> |
197 | 189 | ||
198 | <div [innerHTML]="html.hardwareInformation"></div> | 190 | <div class="block hardware-information" *ngIf="html.hardwareInformation"> |
191 | <div class="anchor" id="hardware-information"></div> | ||
192 | <a | ||
193 | class="anchor-link" | ||
194 | routerLink="/about/instance" | ||
195 | fragment="hardware-information" | ||
196 | #anchorLink | ||
197 | (click)="onClickCopyLink(anchorLink)"> | ||
198 | <h3 i18n class="section-title">Hardware information</h3> | ||
199 | </a> | ||
200 | |||
201 | <div [innerHTML]="html.hardwareInformation"></div> | ||
202 | </div> | ||
199 | </div> | 203 | </div> |
200 | </div> | 204 | </div> |
201 | 205 | ||
202 | <div class="col-md-12 col-xl-6"> | 206 | <div class="col-md-12 col-xl-6" myPluginSelector pluginSelectorId="about-instance-features"> |
203 | <h2 class="sr-only" i18n>FEATURES</h2> | 207 | <h2 class="sr-only" i18n>FEATURES</h2> |
204 | <my-instance-features-table></my-instance-features-table> | 208 | <my-instance-features-table></my-instance-features-table> |
205 | </div> | 209 | </div> |
206 | 210 | ||
207 | <div class="col"> | 211 | <div class="col" myPluginSelector pluginSelectorId="about-instance-statistics"> |
208 | <div class="anchor" id="statistics"></div> | 212 | <div class="anchor" id="statistics"></div> |
209 | <a | 213 | <a |
210 | class="anchor-link" | 214 | class="anchor-link" |
diff --git a/client/src/app/+about/about.component.html b/client/src/app/+about/about.component.html index 1ab00c5df..63d429ebf 100644 --- a/client/src/app/+about/about.component.html +++ b/client/src/app/+about/about.component.html | |||
@@ -2,11 +2,11 @@ | |||
2 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }"> | 2 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }"> |
3 | 3 | ||
4 | <div class="links"> | 4 | <div class="links"> |
5 | <a i18n routerLink="instance" routerLinkActive="active" class="title-page title-page-about">Instance</a> | 5 | <a myPluginSelector pluginSelectorId="about-menu-instance" i18n routerLink="instance" routerLinkActive="active" class="title-page title-page-about">Instance</a> |
6 | 6 | ||
7 | <a i18n routerLink="peertube" routerLinkActive="active" class="title-page title-page-about">PeerTube</a> | 7 | <a myPluginSelector pluginSelectorId="about-menu-peertube" i18n routerLink="peertube" routerLinkActive="active" class="title-page title-page-about">PeerTube</a> |
8 | 8 | ||
9 | <a i18n routerLink="follows" routerLinkActive="active" class="title-page title-page-about">Network</a> | 9 | <a myPluginSelector pluginSelectorId="about-menu-network" i18n routerLink="follows" routerLinkActive="active" class="title-page title-page-about">Network</a> |
10 | </div> | 10 | </div> |
11 | </div> | 11 | </div> |
12 | 12 | ||
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 0bb24de2e..8362e6b7e 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -19,10 +19,8 @@ | |||
19 | ></my-user-moderation-dropdown> | 19 | ></my-user-moderation-dropdown> |
20 | 20 | ||
21 | <span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span> | 21 | <span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span> |
22 | <span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span> | 22 | |
23 | <span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span> | 23 | <my-account-block-badges [account]="account"></my-account-block-badges> |
24 | <span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span> | ||
25 | <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> | ||
26 | </div> | 24 | </div> |
27 | 25 | ||
28 | <div class="actor-handle"> | 26 | <div class="actor-handle"> |
diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss index cdd00487b..5043b98c4 100644 --- a/client/src/app/+accounts/accounts.component.scss +++ b/client/src/app/+accounts/accounts.component.scss | |||
@@ -30,16 +30,10 @@ | |||
30 | } | 30 | } |
31 | } | 31 | } |
32 | 32 | ||
33 | my-user-moderation-dropdown, | 33 | my-user-moderation-dropdown { |
34 | .badge { | 34 | margin: 0 10px; |
35 | @include margin-left(10px); | ||
36 | 35 | ||
37 | position: relative; | 36 | height: fit-content; |
38 | top: 3px; | ||
39 | } | ||
40 | |||
41 | .badge { | ||
42 | font-size: 13px; | ||
43 | } | 37 | } |
44 | 38 | ||
45 | .copy-button { | 39 | .copy-button { |
@@ -64,6 +58,10 @@ my-user-moderation-dropdown, | |||
64 | @include avatar-row-responsive(var(--myImgMargin), var(--myGreyFontSize)); | 58 | @include avatar-row-responsive(var(--myImgMargin), var(--myGreyFontSize)); |
65 | } | 59 | } |
66 | 60 | ||
61 | .actor-display-name { | ||
62 | align-items: center; | ||
63 | } | ||
64 | |||
67 | .description { | 65 | .description { |
68 | grid-column: 1 / 3; | 66 | grid-column: 1 / 3; |
69 | max-width: 1000px; | 67 | max-width: 1000px; |
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index 0dcbc250a..898325492 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts | |||
@@ -12,7 +12,7 @@ import { | |||
12 | VideoChannelService, | 12 | VideoChannelService, |
13 | VideoService | 13 | VideoService |
14 | } from '@app/shared/shared-main' | 14 | } from '@app/shared/shared-main' |
15 | import { AccountReportComponent } from '@app/shared/shared-moderation' | 15 | import { AccountReportComponent, BlocklistService } from '@app/shared/shared-moderation' |
16 | import { HttpStatusCode, User, UserRight } from '@shared/models' | 16 | import { HttpStatusCode, User, UserRight } from '@shared/models' |
17 | 17 | ||
18 | @Component({ | 18 | @Component({ |
@@ -52,6 +52,7 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
52 | private authService: AuthService, | 52 | private authService: AuthService, |
53 | private videoService: VideoService, | 53 | private videoService: VideoService, |
54 | private markdown: MarkdownService, | 54 | private markdown: MarkdownService, |
55 | private blocklist: BlocklistService, | ||
55 | private screenService: ScreenService | 56 | private screenService: ScreenService |
56 | ) { | 57 | ) { |
57 | } | 58 | } |
@@ -159,6 +160,7 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
159 | this.updateModerationActions() | 160 | this.updateModerationActions() |
160 | this.loadUserIfNeeded(account) | 161 | this.loadUserIfNeeded(account) |
161 | this.loadAccountVideosCount() | 162 | this.loadAccountVideosCount() |
163 | this.loadAccountBlockStatus() | ||
162 | } | 164 | } |
163 | 165 | ||
164 | private showReportModal () { | 166 | private showReportModal () { |
@@ -217,4 +219,9 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
217 | this.accountVideosCount = res.total | 219 | this.accountVideosCount = res.total |
218 | }) | 220 | }) |
219 | } | 221 | } |
222 | |||
223 | private loadAccountBlockStatus () { | ||
224 | this.blocklist.getStatus({ accounts: [ this.account.nameWithHostForced ], hosts: [ this.account.host ] }) | ||
225 | .subscribe(status => this.account.updateBlockStatus(status)) | ||
226 | } | ||
220 | } | 227 | } |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html index 318c8e2c2..c9533208a 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html | |||
@@ -56,6 +56,36 @@ | |||
56 | </ng-container> | 56 | </ng-container> |
57 | </div> | 57 | </div> |
58 | 58 | ||
59 | <ng-container formGroupName="client"> | ||
60 | |||
61 | <ng-container formGroupName="videos"> | ||
62 | <ng-container formGroupName="miniature"> | ||
63 | <div class="form-group"> | ||
64 | <my-peertube-checkbox | ||
65 | inputName="clientVideosMiniaturePreferAuthorDisplayName" formControlName="preferAuthorDisplayName" | ||
66 | i18n-labelText labelText="Prefer author display name in video miniature" | ||
67 | ></my-peertube-checkbox> | ||
68 | </div> | ||
69 | </ng-container> | ||
70 | </ng-container> | ||
71 | |||
72 | <ng-container formGroupName="menu"> | ||
73 | <ng-container formGroupName="login"> | ||
74 | <div class="form-group"> | ||
75 | <my-peertube-checkbox | ||
76 | inputName="clientMenuLoginRedirectOnSingleExternalAuth" formControlName="redirectOnSingleExternalAuth" | ||
77 | i18n-labelText labelText="Redirect users on single external auth when users click on the login button in menu" | ||
78 | > | ||
79 | <ng-container ngProjectAs="description"> | ||
80 | <span *ngIf="countExternalAuth() === 0" i18n>⚠️ You don't have any external auth plugin enabled.</span> | ||
81 | <span *ngIf="countExternalAuth() > 1" i18n>⚠️ You have multiple external auth plugins enabled.</span> | ||
82 | </ng-container> | ||
83 | </my-peertube-checkbox> | ||
84 | </div> | ||
85 | </ng-container> | ||
86 | </ng-container> | ||
87 | </ng-container> | ||
88 | |||
59 | </div> | 89 | </div> |
60 | </div> | 90 | </div> |
61 | 91 | ||
@@ -276,7 +306,7 @@ | |||
276 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 306 | <div class="form-group col-12 col-lg-4 col-xl-3"> |
277 | <div i18n class="inner-form-title">VIDEO CHANNELS</div> | 307 | <div i18n class="inner-form-title">VIDEO CHANNELS</div> |
278 | </div> | 308 | </div> |
279 | 309 | ||
280 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 310 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> |
281 | <div class="form-group" formGroupName="videoChannels"> | 311 | <div class="form-group" formGroupName="videoChannels"> |
282 | <label i18n for="videoChannelsMaxPerUser">Max video channels per user</label> | 312 | <label i18n for="videoChannelsMaxPerUser">Max video channels per user</label> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts index 7a8258820..81457bd36 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts | |||
@@ -36,6 +36,10 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges { | |||
36 | } | 36 | } |
37 | } | 37 | } |
38 | 38 | ||
39 | countExternalAuth () { | ||
40 | return this.serverConfig.plugin.registeredExternalAuths.length | ||
41 | } | ||
42 | |||
39 | getVideoQuotaOptions () { | 43 | getVideoQuotaOptions () { |
40 | return this.configService.videoQuotaOptions | 44 | return this.configService.videoQuotaOptions |
41 | } | 45 | } |
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 fdb0a7532..f2eaa3033 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 | |||
@@ -106,6 +106,18 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
106 | whitelisted: null | 106 | whitelisted: null |
107 | } | 107 | } |
108 | }, | 108 | }, |
109 | client: { | ||
110 | videos: { | ||
111 | miniature: { | ||
112 | preferAuthorDisplayName: null | ||
113 | } | ||
114 | }, | ||
115 | menu: { | ||
116 | login: { | ||
117 | redirectOnSingleExternalAuth: null | ||
118 | } | ||
119 | } | ||
120 | }, | ||
109 | cache: { | 121 | cache: { |
110 | previews: { | 122 | previews: { |
111 | size: CACHE_PREVIEWS_SIZE_VALIDATOR | 123 | size: CACHE_PREVIEWS_SIZE_VALIDATOR |
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index 90eea1505..531b06dc9 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html | |||
@@ -48,7 +48,8 @@ | |||
48 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> | 48 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> |
49 | 49 | ||
50 | <div class="additionnal-links"> | 50 | <div class="additionnal-links"> |
51 | <a i18n class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> | 51 | <a i18n role="button" class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> |
52 | |||
52 | <div *ngIf="signupAllowed" class="signup-link"> | 53 | <div *ngIf="signupAllowed" class="signup-link"> |
53 | <span>·</span> | 54 | <span>·</span> |
54 | <a i18n routerLink="/signup" class="create-an-account">Create an account</a> | 55 | <a i18n routerLink="/signup" class="create-an-account">Create an account</a> |
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index 1fa4bd3b5..648b8db36 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { environment } from 'src/environments/environment' | 1 | |
2 | import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core' | 2 | import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' |
@@ -7,6 +7,7 @@ import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/ | |||
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
8 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' | 8 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' |
9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
10 | import { PluginsManager } from '@root-helpers/plugins-manager' | ||
10 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | 11 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' |
11 | 12 | ||
12 | @Component({ | 13 | @Component({ |
@@ -98,7 +99,7 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
98 | } | 99 | } |
99 | 100 | ||
100 | getAuthHref (auth: RegisteredExternalAuthConfig) { | 101 | getAuthHref (auth: RegisteredExternalAuthConfig) { |
101 | return environment.apiUrl + `/plugins/${auth.name}/${auth.version}/auth/${auth.authName}` | 102 | return PluginsManager.getExternalAuthHref(auth) |
102 | } | 103 | } |
103 | 104 | ||
104 | login () { | 105 | login () { |
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html index 064fbb6f5..aec2e373c 100644 --- a/client/src/app/+video-channels/video-channels.component.html +++ b/client/src/app/+video-channels/video-channels.component.html | |||
@@ -23,14 +23,16 @@ | |||
23 | <div class="section-label" i18n>OWNER ACCOUNT</div> | 23 | <div class="section-label" i18n>OWNER ACCOUNT</div> |
24 | 24 | ||
25 | <div class="avatar-row"> | 25 | <div class="avatar-row"> |
26 | <my-actor-avatar class="account-avatar" [account]="videoChannel.ownerAccount" [internalHref]="getAccountUrl()"></my-actor-avatar> | 26 | <my-actor-avatar class="account-avatar" [account]="ownerAccount" [internalHref]="getAccountUrl()"></my-actor-avatar> |
27 | 27 | ||
28 | <div class="actor-info"> | 28 | <div class="actor-info"> |
29 | <h4> | 29 | <h4> |
30 | <a [routerLink]="getAccountUrl()" title="View account" i18n-title>{{ videoChannel.ownerAccount.displayName }}</a> | 30 | <a [routerLink]="getAccountUrl()" title="View account" i18n-title>{{ ownerAccount.displayName }}</a> |
31 | </h4> | 31 | </h4> |
32 | 32 | ||
33 | <div class="actor-handle">@{{ videoChannel.ownerBy }}</div> | 33 | <div class="actor-handle">@{{ videoChannel.ownerBy }}</div> |
34 | |||
35 | <my-account-block-badges [account]="ownerAccount"></my-account-block-badges> | ||
34 | </div> | 36 | </div> |
35 | </div> | 37 | </div> |
36 | 38 | ||
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts index 272fc41d9..ebb991f4e 100644 --- a/client/src/app/+video-channels/video-channels.component.ts +++ b/client/src/app/+video-channels/video-channels.component.ts | |||
@@ -4,7 +4,8 @@ import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators | |||
4 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' | 4 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' |
5 | import { ActivatedRoute } from '@angular/router' | 5 | import { ActivatedRoute } from '@angular/router' |
6 | import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } from '@app/core' | 6 | import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } from '@app/core' |
7 | import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | 7 | import { Account, ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' |
8 | import { BlocklistService } from '@app/shared/shared-moderation' | ||
8 | import { SupportModalComponent } from '@app/shared/shared-support-modal' | 9 | import { SupportModalComponent } from '@app/shared/shared-support-modal' |
9 | import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' | 10 | import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' |
10 | import { HttpStatusCode } from '@shared/models' | 11 | import { HttpStatusCode } from '@shared/models' |
@@ -18,6 +19,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { | |||
18 | @ViewChild('supportModal') supportModal: SupportModalComponent | 19 | @ViewChild('supportModal') supportModal: SupportModalComponent |
19 | 20 | ||
20 | videoChannel: VideoChannel | 21 | videoChannel: VideoChannel |
22 | ownerAccount: Account | ||
21 | hotkeys: Hotkey[] | 23 | hotkeys: Hotkey[] |
22 | links: ListOverflowItem[] = [] | 24 | links: ListOverflowItem[] = [] |
23 | isChannelManageable = false | 25 | isChannelManageable = false |
@@ -38,7 +40,8 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { | |||
38 | private restExtractor: RestExtractor, | 40 | private restExtractor: RestExtractor, |
39 | private hotkeysService: HotkeysService, | 41 | private hotkeysService: HotkeysService, |
40 | private screenService: ScreenService, | 42 | private screenService: ScreenService, |
41 | private markdown: MarkdownService | 43 | private markdown: MarkdownService, |
44 | private blocklist: BlocklistService | ||
42 | ) { } | 45 | ) { } |
43 | 46 | ||
44 | ngOnInit () { | 47 | ngOnInit () { |
@@ -58,8 +61,10 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { | |||
58 | 61 | ||
59 | // After the markdown renderer to avoid layout changes | 62 | // After the markdown renderer to avoid layout changes |
60 | this.videoChannel = videoChannel | 63 | this.videoChannel = videoChannel |
64 | this.ownerAccount = new Account(this.videoChannel.ownerAccount) | ||
61 | 65 | ||
62 | this.loadChannelVideosCount() | 66 | this.loadChannelVideosCount() |
67 | this.loadOwnerBlockStatus() | ||
63 | }) | 68 | }) |
64 | 69 | ||
65 | this.hotkeys = [ | 70 | this.hotkeys = [ |
@@ -125,4 +130,9 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { | |||
125 | sort: '-publishedAt' | 130 | sort: '-publishedAt' |
126 | }).subscribe(res => this.channelVideosCount = res.total) | 131 | }).subscribe(res => this.channelVideosCount = res.total) |
127 | } | 132 | } |
133 | |||
134 | private loadOwnerBlockStatus () { | ||
135 | this.blocklist.getStatus({ accounts: [ this.ownerAccount.nameWithHostForced ], hosts: [ this.ownerAccount.host ] }) | ||
136 | .subscribe(status => this.ownerAccount.updateBlockStatus(status)) | ||
137 | } | ||
128 | } | 138 | } |
diff --git a/client/src/app/+video-channels/video-channels.module.ts b/client/src/app/+video-channels/video-channels.module.ts index 35c39cc2e..76aaecf83 100644 --- a/client/src/app/+video-channels/video-channels.module.ts +++ b/client/src/app/+video-channels/video-channels.module.ts | |||
@@ -2,15 +2,16 @@ import { NgModule } from '@angular/core' | |||
2 | import { SharedFormModule } from '@app/shared/shared-forms' | 2 | import { SharedFormModule } from '@app/shared/shared-forms' |
3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' | 3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' |
4 | import { SharedMainModule } from '@app/shared/shared-main' | 4 | import { SharedMainModule } from '@app/shared/shared-main' |
5 | import { SharedModerationModule } from '@app/shared/shared-moderation' | ||
5 | import { SharedSupportModal } from '@app/shared/shared-support-modal' | 6 | import { SharedSupportModal } from '@app/shared/shared-support-modal' |
6 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' | 7 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' |
7 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' | 8 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' |
8 | import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' | 9 | import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' |
10 | import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module' | ||
9 | import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component' | 11 | import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component' |
10 | import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' | 12 | import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' |
11 | import { VideoChannelsRoutingModule } from './video-channels-routing.module' | 13 | import { VideoChannelsRoutingModule } from './video-channels-routing.module' |
12 | import { VideoChannelsComponent } from './video-channels.component' | 14 | import { VideoChannelsComponent } from './video-channels.component' |
13 | import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module' | ||
14 | 15 | ||
15 | @NgModule({ | 16 | @NgModule({ |
16 | imports: [ | 17 | imports: [ |
@@ -23,7 +24,8 @@ import { SharedActorImageModule } from '../shared/shared-actor-image/shared-acto | |||
23 | SharedUserSubscriptionModule, | 24 | SharedUserSubscriptionModule, |
24 | SharedGlobalIconModule, | 25 | SharedGlobalIconModule, |
25 | SharedSupportModal, | 26 | SharedSupportModal, |
26 | SharedActorImageModule | 27 | SharedActorImageModule, |
28 | SharedModerationModule | ||
27 | ], | 29 | ], |
28 | 30 | ||
29 | declarations: [ | 31 | declarations: [ |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 46dd807ec..9ea991042 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -30,7 +30,10 @@ | |||
30 | 30 | ||
31 | <div class="dropdown-divider"></div> | 31 | <div class="dropdown-divider"></div> |
32 | 32 | ||
33 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()"> | 33 | <a |
34 | myPluginSelector pluginSelectorId="menu-user-dropdown-language-item" | ||
35 | ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()" | ||
36 | > | ||
34 | <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> | 37 | <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> |
35 | <span i18n>Interface:</span> | 38 | <span i18n>Interface:</span> |
36 | <span class="ml-auto text-muted">{{ currentInterfaceLanguage }}</span> | 39 | <span class="ml-auto text-muted">{{ currentInterfaceLanguage }}</span> |
@@ -96,7 +99,9 @@ | |||
96 | </div> | 99 | </div> |
97 | 100 | ||
98 | <div *ngIf="!isLoggedIn" class="login-buttons-block"> | 101 | <div *ngIf="!isLoggedIn" class="login-buttons-block"> |
99 | <a i18n routerLink="/login" class="peertube-button-link orange-button">Login</a> | 102 | <a i18n *ngIf="!getExternalLoginHref()" routerLink="/login" class="peertube-button-link orange-button">Login</a> |
103 | <a i18n *ngIf="getExternalLoginHref()" [href]="getExternalLoginHref()" class="peertube-button-link orange-button">Login</a> | ||
104 | |||
100 | <a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="peertube-button-link create-account-button">Create an account</a> | 105 | <a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="peertube-button-link create-account-button">Create an account</a> |
101 | </div> | 106 | </div> |
102 | 107 | ||
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 97f07c956..d5ddc29cb 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts | |||
@@ -21,6 +21,7 @@ import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | |||
21 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | 21 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' |
22 | import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service' | 22 | import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service' |
23 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | 23 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' |
24 | import { PluginsManager } from '@root-helpers/plugins-manager' | ||
24 | import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models' | 25 | import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models' |
25 | 26 | ||
26 | const logger = debug('peertube:menu:MenuComponent') | 27 | const logger = debug('peertube:menu:MenuComponent') |
@@ -129,6 +130,15 @@ export class MenuComponent implements OnInit { | |||
129 | .subscribe(() => this.openQuickSettings()) | 130 | .subscribe(() => this.openQuickSettings()) |
130 | } | 131 | } |
131 | 132 | ||
133 | getExternalLoginHref () { | ||
134 | if (!this.serverConfig || this.serverConfig.client.menu.login.redirectOnSingleExternalAuth !== true) return undefined | ||
135 | |||
136 | const externalAuths = this.serverConfig.plugin.registeredExternalAuths | ||
137 | if (externalAuths.length !== 1) return undefined | ||
138 | |||
139 | return PluginsManager.getExternalAuthHref(externalAuths[0]) | ||
140 | } | ||
141 | |||
132 | isRegistrationAllowed () { | 142 | isRegistrationAllowed () { |
133 | if (!this.serverConfig) return false | 143 | if (!this.serverConfig) return false |
134 | 144 | ||
diff --git a/client/src/app/shared/shared-main/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts index 92606e7fa..8b78d01a6 100644 --- a/client/src/app/shared/shared-main/account/account.model.ts +++ b/client/src/app/shared/shared-main/account/account.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Account as ServerAccount, ActorImage } from '@shared/models' | 1 | import { Account as ServerAccount, ActorImage, BlockStatus } from '@shared/models' |
2 | import { Actor } from './actor.model' | 2 | import { Actor } from './actor.model' |
3 | 3 | ||
4 | export class Account extends Actor implements ServerAccount { | 4 | export class Account extends Actor implements ServerAccount { |
@@ -49,4 +49,11 @@ export class Account extends Actor implements ServerAccount { | |||
49 | resetAvatar () { | 49 | resetAvatar () { |
50 | this.avatar = null | 50 | this.avatar = null |
51 | } | 51 | } |
52 | |||
53 | updateBlockStatus (blockStatus: BlockStatus) { | ||
54 | this.mutedByInstance = blockStatus.accounts[this.nameWithHostForced].blockedByServer | ||
55 | this.mutedByUser = blockStatus.accounts[this.nameWithHostForced].blockedByUser | ||
56 | this.mutedServerByUser = blockStatus.hosts[this.host].blockedByUser | ||
57 | this.mutedServerByInstance = blockStatus.hosts[this.host].blockedByServer | ||
58 | } | ||
52 | } | 59 | } |
diff --git a/client/src/app/shared/shared-moderation/account-block-badges.component.html b/client/src/app/shared/shared-moderation/account-block-badges.component.html new file mode 100644 index 000000000..feac707c2 --- /dev/null +++ b/client/src/app/shared/shared-moderation/account-block-badges.component.html | |||
@@ -0,0 +1,4 @@ | |||
1 | <span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span> | ||
2 | <span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span> | ||
3 | <span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span> | ||
4 | <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> | ||
diff --git a/client/src/app/shared/shared-moderation/account-block-badges.component.scss b/client/src/app/shared/shared-moderation/account-block-badges.component.scss new file mode 100644 index 000000000..ccc3666aa --- /dev/null +++ b/client/src/app/shared/shared-moderation/account-block-badges.component.scss | |||
@@ -0,0 +1,9 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | |||
4 | .badge { | ||
5 | @include margin-right(10px); | ||
6 | |||
7 | height: fit-content; | ||
8 | font-size: 12px; | ||
9 | } | ||
diff --git a/client/src/app/shared/shared-moderation/account-block-badges.component.ts b/client/src/app/shared/shared-moderation/account-block-badges.component.ts new file mode 100644 index 000000000..a72601118 --- /dev/null +++ b/client/src/app/shared/shared-moderation/account-block-badges.component.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { Account } from '../shared-main' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-account-block-badges', | ||
6 | styleUrls: [ './account-block-badges.component.scss' ], | ||
7 | templateUrl: './account-block-badges.component.html' | ||
8 | }) | ||
9 | export class AccountBlockBadgesComponent { | ||
10 | @Input() account: Account | ||
11 | } | ||
diff --git a/client/src/app/shared/shared-moderation/blocklist.service.ts b/client/src/app/shared/shared-moderation/blocklist.service.ts index db2a8c584..f4836c6c4 100644 --- a/client/src/app/shared/shared-moderation/blocklist.service.ts +++ b/client/src/app/shared/shared-moderation/blocklist.service.ts | |||
@@ -3,7 +3,7 @@ import { catchError, map } from 'rxjs/operators' | |||
3 | import { HttpClient, HttpParams } from '@angular/common/http' | 3 | import { HttpClient, HttpParams } from '@angular/common/http' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { RestExtractor, RestPagination, RestService } from '@app/core' | 5 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
6 | import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '@shared/models' | 6 | import { AccountBlock as AccountBlockServer, BlockStatus, ResultList, ServerBlock } from '@shared/models' |
7 | import { environment } from '../../../environments/environment' | 7 | import { environment } from '../../../environments/environment' |
8 | import { Account } from '../shared-main' | 8 | import { Account } from '../shared-main' |
9 | import { AccountBlock } from './account-block.model' | 9 | import { AccountBlock } from './account-block.model' |
@@ -12,6 +12,7 @@ export enum BlocklistComponentType { Account, Instance } | |||
12 | 12 | ||
13 | @Injectable() | 13 | @Injectable() |
14 | export class BlocklistService { | 14 | export class BlocklistService { |
15 | static BASE_BLOCKLIST_URL = environment.apiUrl + '/api/v1/blocklist' | ||
15 | static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist' | 16 | static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist' |
16 | static BASE_SERVER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/server/blocklist' | 17 | static BASE_SERVER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/server/blocklist' |
17 | 18 | ||
@@ -21,6 +22,23 @@ export class BlocklistService { | |||
21 | private restService: RestService | 22 | private restService: RestService |
22 | ) { } | 23 | ) { } |
23 | 24 | ||
25 | /** ********************* Blocklist status ***********************/ | ||
26 | |||
27 | getStatus (options: { | ||
28 | accounts?: string[] | ||
29 | hosts?: string[] | ||
30 | }) { | ||
31 | const { accounts, hosts } = options | ||
32 | |||
33 | let params = new HttpParams() | ||
34 | |||
35 | if (accounts) params = this.restService.addArrayParams(params, 'accounts', accounts) | ||
36 | if (hosts) params = this.restService.addArrayParams(params, 'hosts', hosts) | ||
37 | |||
38 | return this.authHttp.get<BlockStatus>(BlocklistService.BASE_BLOCKLIST_URL + '/status', { params }) | ||
39 | .pipe(catchError(err => this.restExtractor.handleError(err))) | ||
40 | } | ||
41 | |||
24 | /** ********************* User -> Account blocklist ***********************/ | 42 | /** ********************* User -> Account blocklist ***********************/ |
25 | 43 | ||
26 | getUserAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) { | 44 | getUserAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) { |
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts index 41c910ffe..da85b2299 100644 --- a/client/src/app/shared/shared-moderation/index.ts +++ b/client/src/app/shared/shared-moderation/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './report-modals' | 1 | export * from './report-modals' |
2 | 2 | ||
3 | export * from './abuse.service' | 3 | export * from './abuse.service' |
4 | export * from './account-block-badges.component' | ||
4 | export * from './account-block.model' | 5 | export * from './account-block.model' |
5 | export * from './account-blocklist.component' | 6 | export * from './account-blocklist.component' |
6 | export * from './batch-domains-modal.component' | 7 | export * from './batch-domains-modal.component' |
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts index 95213e2bd..7cadda67c 100644 --- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts +++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts | |||
@@ -13,6 +13,7 @@ import { UserBanModalComponent } from './user-ban-modal.component' | |||
13 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' | 13 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' |
14 | import { VideoBlockComponent } from './video-block.component' | 14 | import { VideoBlockComponent } from './video-block.component' |
15 | import { VideoBlockService } from './video-block.service' | 15 | import { VideoBlockService } from './video-block.service' |
16 | import { AccountBlockBadgesComponent } from './account-block-badges.component' | ||
16 | import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' | 17 | import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' |
17 | 18 | ||
18 | @NgModule({ | 19 | @NgModule({ |
@@ -31,7 +32,8 @@ import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image | |||
31 | VideoReportComponent, | 32 | VideoReportComponent, |
32 | BatchDomainsModalComponent, | 33 | BatchDomainsModalComponent, |
33 | CommentReportComponent, | 34 | CommentReportComponent, |
34 | AccountReportComponent | 35 | AccountReportComponent, |
36 | AccountBlockBadgesComponent | ||
35 | ], | 37 | ], |
36 | 38 | ||
37 | exports: [ | 39 | exports: [ |
@@ -41,7 +43,8 @@ import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image | |||
41 | VideoReportComponent, | 43 | VideoReportComponent, |
42 | BatchDomainsModalComponent, | 44 | BatchDomainsModalComponent, |
43 | CommentReportComponent, | 45 | CommentReportComponent, |
44 | AccountReportComponent | 46 | AccountReportComponent, |
47 | AccountBlockBadgesComponent | ||
45 | ], | 48 | ], |
46 | 49 | ||
47 | providers: [ | 50 | providers: [ |
diff --git a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts index b18d861d6..e2cd2cdc1 100644 --- a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts | |||
@@ -289,13 +289,13 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
289 | { | 289 | { |
290 | label: $localize`Mute the instance`, | 290 | label: $localize`Mute the instance`, |
291 | description: $localize`Hide any content from that instance for you.`, | 291 | description: $localize`Hide any content from that instance for you.`, |
292 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false, | 292 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByUser === false, |
293 | handler: ({ account }) => this.blockServerByUser(account.host) | 293 | handler: ({ account }) => this.blockServerByUser(account.host) |
294 | }, | 294 | }, |
295 | { | 295 | { |
296 | label: $localize`Unmute the instance`, | 296 | label: $localize`Unmute the instance`, |
297 | description: $localize`Show back content from that instance for you.`, | 297 | description: $localize`Show back content from that instance for you.`, |
298 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, | 298 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByUser === true, |
299 | handler: ({ account }) => this.unblockServerByUser(account.host) | 299 | handler: ({ account }) => this.unblockServerByUser(account.host) |
300 | }, | 300 | }, |
301 | { | 301 | { |
diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts index 71c31696a..421ce4934 100644 --- a/client/src/assets/player/p2p-media-loader/hls-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts | |||
@@ -146,7 +146,10 @@ class Html5Hlsjs { | |||
146 | } | 146 | } |
147 | 147 | ||
148 | duration () { | 148 | duration () { |
149 | return this._duration || this.videoElement.duration || 0 | 149 | if (this._duration === Infinity) return Infinity |
150 | if (!isNaN(this.videoElement.duration)) return this.videoElement.duration | ||
151 | |||
152 | return this._duration || 0 | ||
150 | } | 153 | } |
151 | 154 | ||
152 | seekable () { | 155 | seekable () { |
@@ -366,6 +369,7 @@ class Html5Hlsjs { | |||
366 | 369 | ||
367 | this.isLive = data.details.live | 370 | this.isLive = data.details.live |
368 | this.dvrDuration = data.details.totalduration | 371 | this.dvrDuration = data.details.totalduration |
372 | |||
369 | this._duration = this.isLive ? Infinity : data.details.totalduration | 373 | this._duration = this.isLive ? Infinity : data.details.totalduration |
370 | }) | 374 | }) |
371 | 375 | ||
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts index 0121e87d7..451b4a161 100644 --- a/client/src/assets/player/peertube-plugin.ts +++ b/client/src/assets/player/peertube-plugin.ts | |||
@@ -11,6 +11,7 @@ import { | |||
11 | } from './peertube-player-local-storage' | 11 | } from './peertube-player-local-storage' |
12 | import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings' | 12 | import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings' |
13 | import { isMobile } from './utils' | 13 | import { isMobile } from './utils' |
14 | import { SettingsButton } from './videojs-components/settings-menu-button' | ||
14 | 15 | ||
15 | const Plugin = videojs.getPlugin('plugin') | 16 | const Plugin = videojs.getPlugin('plugin') |
16 | 17 | ||
@@ -31,7 +32,8 @@ class PeerTubePlugin extends Plugin { | |||
31 | 32 | ||
32 | private menuOpened = false | 33 | private menuOpened = false |
33 | private mouseInControlBar = false | 34 | private mouseInControlBar = false |
34 | private readonly savedInactivityTimeout: number | 35 | private mouseInSettings = false |
36 | private readonly initialInactivityTimeout: number | ||
35 | 37 | ||
36 | constructor (player: videojs.Player, options?: PeerTubePluginOptions) { | 38 | constructor (player: videojs.Player, options?: PeerTubePluginOptions) { |
37 | super(player) | 39 | super(player) |
@@ -40,8 +42,7 @@ class PeerTubePlugin extends Plugin { | |||
40 | this.videoDuration = options.videoDuration | 42 | this.videoDuration = options.videoDuration |
41 | this.videoCaptions = options.videoCaptions | 43 | this.videoCaptions = options.videoCaptions |
42 | this.isLive = options.isLive | 44 | this.isLive = options.isLive |
43 | 45 | this.initialInactivityTimeout = this.player.options_.inactivityTimeout | |
44 | this.savedInactivityTimeout = player.options_.inactivityTimeout | ||
45 | 46 | ||
46 | if (options.autoplay) this.player.addClass('vjs-has-autoplay') | 47 | if (options.autoplay) this.player.addClass('vjs-has-autoplay') |
47 | 48 | ||
@@ -108,13 +109,13 @@ class PeerTubePlugin extends Plugin { | |||
108 | if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval) | 109 | if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval) |
109 | } | 110 | } |
110 | 111 | ||
111 | onMenuOpen () { | 112 | onMenuOpened () { |
112 | this.menuOpened = false | 113 | this.menuOpened = true |
113 | this.alterInactivity() | 114 | this.alterInactivity() |
114 | } | 115 | } |
115 | 116 | ||
116 | onMenuClosed () { | 117 | onMenuClosed () { |
117 | this.menuOpened = true | 118 | this.menuOpened = false |
118 | this.alterInactivity() | 119 | this.alterInactivity() |
119 | } | 120 | } |
120 | 121 | ||
@@ -126,6 +127,8 @@ class PeerTubePlugin extends Plugin { | |||
126 | this.initCaptions() | 127 | this.initCaptions() |
127 | 128 | ||
128 | this.listenControlBarMouse() | 129 | this.listenControlBarMouse() |
130 | |||
131 | this.listenFullScreenChange() | ||
129 | } | 132 | } |
130 | 133 | ||
131 | private runViewAdd () { | 134 | private runViewAdd () { |
@@ -198,27 +201,50 @@ class PeerTubePlugin extends Plugin { | |||
198 | return fetch(url, { method: 'PUT', body, headers }) | 201 | return fetch(url, { method: 'PUT', body, headers }) |
199 | } | 202 | } |
200 | 203 | ||
204 | private listenFullScreenChange () { | ||
205 | this.player.on('fullscreenchange', () => { | ||
206 | if (this.player.isFullscreen()) this.player.focus() | ||
207 | }) | ||
208 | } | ||
209 | |||
201 | private listenControlBarMouse () { | 210 | private listenControlBarMouse () { |
202 | this.player.controlBar.on('mouseenter', () => { | 211 | const controlBar = this.player.controlBar |
212 | const settingsButton: SettingsButton = (controlBar as any).settingsButton | ||
213 | |||
214 | controlBar.on('mouseenter', () => { | ||
203 | this.mouseInControlBar = true | 215 | this.mouseInControlBar = true |
204 | this.alterInactivity() | 216 | this.alterInactivity() |
205 | }) | 217 | }) |
206 | 218 | ||
207 | this.player.controlBar.on('mouseleave', () => { | 219 | controlBar.on('mouseleave', () => { |
208 | this.mouseInControlBar = false | 220 | this.mouseInControlBar = false |
209 | this.alterInactivity() | 221 | this.alterInactivity() |
210 | }) | 222 | }) |
223 | |||
224 | settingsButton.dialog.on('mouseenter', () => { | ||
225 | this.mouseInSettings = true | ||
226 | this.alterInactivity() | ||
227 | }) | ||
228 | |||
229 | settingsButton.dialog.on('mouseleave', () => { | ||
230 | this.mouseInSettings = false | ||
231 | this.alterInactivity() | ||
232 | }) | ||
211 | } | 233 | } |
212 | 234 | ||
213 | private alterInactivity () { | 235 | private alterInactivity () { |
214 | if (this.menuOpened) { | 236 | if (this.menuOpened || this.mouseInSettings || this.mouseInControlBar || this.isTouchEnabled()) { |
215 | this.player.options_.inactivityTimeout = this.savedInactivityTimeout | 237 | this.setInactivityTimeout(0) |
216 | return | 238 | return |
217 | } | 239 | } |
218 | 240 | ||
219 | if (!this.mouseInControlBar && !this.isTouchEnabled()) { | 241 | this.setInactivityTimeout(this.initialInactivityTimeout) |
220 | this.player.options_.inactivityTimeout = 1 | 242 | this.player.reportUserActivity(true) |
221 | } | 243 | } |
244 | |||
245 | private setInactivityTimeout (timeout: number) { | ||
246 | (this.player as any).cache_.inactivityTimeout = timeout | ||
247 | this.player.options_.inactivityTimeout = timeout | ||
222 | } | 248 | } |
223 | 249 | ||
224 | private isTouchEnabled () { | 250 | private isTouchEnabled () { |
diff --git a/client/src/assets/player/videojs-components/settings-menu-button.ts b/client/src/assets/player/videojs-components/settings-menu-button.ts index 75a5c6904..6de390f4d 100644 --- a/client/src/assets/player/videojs-components/settings-menu-button.ts +++ b/client/src/assets/player/videojs-components/settings-menu-button.ts | |||
@@ -144,7 +144,7 @@ class SettingsButton extends Button { | |||
144 | } | 144 | } |
145 | 145 | ||
146 | showDialog () { | 146 | showDialog () { |
147 | this.player().peertube().onMenuOpen(); | 147 | this.player().peertube().onMenuOpened(); |
148 | 148 | ||
149 | (this.menu.el() as HTMLElement).style.opacity = '1' | 149 | (this.menu.el() as HTMLElement).style.opacity = '1' |
150 | 150 | ||
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts index a1b763ff2..9cba63373 100644 --- a/client/src/root-helpers/plugins-manager.ts +++ b/client/src/root-helpers/plugins-manager.ts | |||
@@ -15,6 +15,7 @@ import { | |||
15 | RegisterClientHookOptions, | 15 | RegisterClientHookOptions, |
16 | RegisterClientSettingsScript, | 16 | RegisterClientSettingsScript, |
17 | RegisterClientVideoFieldOptions, | 17 | RegisterClientVideoFieldOptions, |
18 | RegisteredExternalAuthConfig, | ||
18 | ServerConfigPlugin | 19 | ServerConfigPlugin |
19 | } from '../../../shared/models' | 20 | } from '../../../shared/models' |
20 | import { environment } from '../environments/environment' | 21 | import { environment } from '../environments/environment' |
@@ -78,6 +79,11 @@ class PluginsManager { | |||
78 | return isTheme ? '/themes' : '/plugins' | 79 | return isTheme ? '/themes' : '/plugins' |
79 | } | 80 | } |
80 | 81 | ||
82 | static getExternalAuthHref (auth: RegisteredExternalAuthConfig) { | ||
83 | return environment.apiUrl + `/plugins/${auth.name}/${auth.version}/auth/${auth.authName}` | ||
84 | |||
85 | } | ||
86 | |||
81 | loadPluginsList (config: HTMLServerConfig) { | 87 | loadPluginsList (config: HTMLServerConfig) { |
82 | for (const plugin of config.plugin.registered) { | 88 | for (const plugin of config.plugin.registered) { |
83 | this.addPlugin(plugin) | 89 | this.addPlugin(plugin) |
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 96d752699..332a0e17d 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss | |||
@@ -71,7 +71,7 @@ body { | |||
71 | height: $big-play-height; | 71 | height: $big-play-height; |
72 | line-height: $big-play-height; | 72 | line-height: $big-play-height; |
73 | margin-top: -(math.div($big-play-height, 2)); | 73 | margin-top: -(math.div($big-play-height, 2)); |
74 | transition: 0.4s opacity; | 74 | transition: 0.2s background-color; |
75 | 75 | ||
76 | &::-moz-focus-inner { | 76 | &::-moz-focus-inner { |
77 | border: 0; | 77 | border: 0; |
@@ -89,30 +89,6 @@ body { | |||
89 | &:hover { | 89 | &:hover { |
90 | background-color: var(--mainColor, #696969); | 90 | background-color: var(--mainColor, #696969); |
91 | } | 91 | } |
92 | |||
93 | } | ||
94 | |||
95 | // Small effect when we click on the play button | ||
96 | &.vjs-has-big-play-button-clicked { | ||
97 | |||
98 | .vjs-big-play-button, | ||
99 | .vjs-poster { | ||
100 | display: block; | ||
101 | visibility: hidden; | ||
102 | |||
103 | &.vjs-big-play-button, | ||
104 | &.vjs-big-play-button::before { | ||
105 | opacity: 0; | ||
106 | transition: visibility 0.2s, opacity 0.2s; | ||
107 | } | ||
108 | |||
109 | &.vjs-poster, | ||
110 | &.vjs-poster::before { | ||
111 | opacity: 0; | ||
112 | transition: visibility 0.3s, opacity 0.3s; | ||
113 | transition-delay: 0.05s; | ||
114 | } | ||
115 | } | ||
116 | } | 92 | } |
117 | 93 | ||
118 | // Show poster and controls when playing audio-only content | 94 | // Show poster and controls when playing audio-only content |
@@ -158,6 +134,7 @@ body { | |||
158 | background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.6)); | 134 | background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.6)); |
159 | box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2); | 135 | box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2); |
160 | text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); | 136 | text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); |
137 | transition: visibility 0.3s, opacity 0.3s !important; | ||
161 | 138 | ||
162 | > button:first-child { | 139 | > button:first-child { |
163 | @include margin-left($first-control-bar-element-margin-left); | 140 | @include margin-left($first-control-bar-element-margin-left); |