aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.html128
-rw-r--r--client/src/app/+about/about.component.html6
-rw-r--r--client/src/app/+accounts/accounts.component.html6
-rw-r--r--client/src/app/+accounts/accounts.component.scss16
-rw-r--r--client/src/app/+accounts/accounts.component.ts9
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html32
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts4
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts12
-rw-r--r--client/src/app/+login/login.component.html3
-rw-r--r--client/src/app/+login/login.component.ts5
-rw-r--r--client/src/app/+video-channels/video-channels.component.html6
-rw-r--r--client/src/app/+video-channels/video-channels.component.ts14
-rw-r--r--client/src/app/+video-channels/video-channels.module.ts6
-rw-r--r--client/src/app/menu/menu.component.html9
-rw-r--r--client/src/app/menu/menu.component.ts10
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts9
-rw-r--r--client/src/app/shared/shared-moderation/account-block-badges.component.html4
-rw-r--r--client/src/app/shared/shared-moderation/account-block-badges.component.scss9
-rw-r--r--client/src/app/shared/shared-moderation/account-block-badges.component.ts11
-rw-r--r--client/src/app/shared/shared-moderation/blocklist.service.ts20
-rw-r--r--client/src/app/shared/shared-moderation/index.ts1
-rw-r--r--client/src/app/shared/shared-moderation/shared-moderation.module.ts7
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts4
-rw-r--r--client/src/assets/player/p2p-media-loader/hls-plugin.ts5
-rw-r--r--client/src/root-helpers/plugins-manager.ts6
25 files changed, 244 insertions, 98 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
33my-user-moderation-dropdown, 33my-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'
15import { AccountReportComponent } from '@app/shared/shared-moderation' 15import { AccountReportComponent, BlocklistService } from '@app/shared/shared-moderation'
16import { HttpStatusCode, User, UserRight } from '@shared/models' 16import { 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 @@
1import { environment } from 'src/environments/environment' 1
2import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core' 2import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute } from '@angular/router' 3import { ActivatedRoute } from '@angular/router'
4import { AuthService, Notifier, RedirectService, UserService } from '@app/core' 4import { AuthService, Notifier, RedirectService, UserService } from '@app/core'
@@ -7,6 +7,7 @@ import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
8import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' 8import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
9import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' 9import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
10import { PluginsManager } from '@root-helpers/plugins-manager'
10import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' 11import { 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
4import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' 4import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
5import { ActivatedRoute } from '@angular/router' 5import { ActivatedRoute } from '@angular/router'
6import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } from '@app/core' 6import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } from '@app/core'
7import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' 7import { Account, ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
8import { BlocklistService } from '@app/shared/shared-moderation'
8import { SupportModalComponent } from '@app/shared/shared-support-modal' 9import { SupportModalComponent } from '@app/shared/shared-support-modal'
9import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' 10import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
10import { HttpStatusCode } from '@shared/models' 11import { 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'
2import { SharedFormModule } from '@app/shared/shared-forms' 2import { SharedFormModule } from '@app/shared/shared-forms'
3import { SharedGlobalIconModule } from '@app/shared/shared-icons' 3import { SharedGlobalIconModule } from '@app/shared/shared-icons'
4import { SharedMainModule } from '@app/shared/shared-main' 4import { SharedMainModule } from '@app/shared/shared-main'
5import { SharedModerationModule } from '@app/shared/shared-moderation'
5import { SharedSupportModal } from '@app/shared/shared-support-modal' 6import { SharedSupportModal } from '@app/shared/shared-support-modal'
6import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' 7import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription'
7import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' 8import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
8import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' 9import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist'
10import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
9import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component' 11import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component'
10import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' 12import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
11import { VideoChannelsRoutingModule } from './video-channels-routing.module' 13import { VideoChannelsRoutingModule } from './video-channels-routing.module'
12import { VideoChannelsComponent } from './video-channels.component' 14import { VideoChannelsComponent } from './video-channels.component'
13import { 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..bcc884878 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'
21import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' 21import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
22import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service' 22import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service'
23import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' 23import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
24import { PluginsManager } from '@root-helpers/plugins-manager'
24import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models' 25import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models'
25 26
26const logger = debug('peertube:menu:MenuComponent') 27const 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.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 @@
1import { Account as ServerAccount, ActorImage } from '@shared/models' 1import { Account as ServerAccount, ActorImage, BlockStatus } from '@shared/models'
2import { Actor } from './actor.model' 2import { Actor } from './actor.model'
3 3
4export class Account extends Actor implements ServerAccount { 4export 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 @@
1import { Component, Input } from '@angular/core'
2import { 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})
9export 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'
3import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { RestExtractor, RestPagination, RestService } from '@app/core' 5import { RestExtractor, RestPagination, RestService } from '@app/core'
6import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '@shared/models' 6import { AccountBlock as AccountBlockServer, BlockStatus, ResultList, ServerBlock } from '@shared/models'
7import { environment } from '../../../environments/environment' 7import { environment } from '../../../environments/environment'
8import { Account } from '../shared-main' 8import { Account } from '../shared-main'
9import { AccountBlock } from './account-block.model' 9import { AccountBlock } from './account-block.model'
@@ -12,6 +12,7 @@ export enum BlocklistComponentType { Account, Instance }
12 12
13@Injectable() 13@Injectable()
14export class BlocklistService { 14export 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 @@
1export * from './report-modals' 1export * from './report-modals'
2 2
3export * from './abuse.service' 3export * from './abuse.service'
4export * from './account-block-badges.component'
4export * from './account-block.model' 5export * from './account-block.model'
5export * from './account-blocklist.component' 6export * from './account-blocklist.component'
6export * from './batch-domains-modal.component' 7export * 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'
13import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' 13import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
14import { VideoBlockComponent } from './video-block.component' 14import { VideoBlockComponent } from './video-block.component'
15import { VideoBlockService } from './video-block.service' 15import { VideoBlockService } from './video-block.service'
16import { AccountBlockBadgesComponent } from './account-block-badges.component'
16import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' 17import { 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..2aa691f05 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,9 @@ class Html5Hlsjs {
146 } 146 }
147 147
148 duration () { 148 duration () {
149 return this._duration || this.videoElement.duration || 0 149 if (!isNaN(this.videoElement.duration)) return this.videoElement.duration
150
151 return this._duration || 0
150 } 152 }
151 153
152 seekable () { 154 seekable () {
@@ -366,6 +368,7 @@ class Html5Hlsjs {
366 368
367 this.isLive = data.details.live 369 this.isLive = data.details.live
368 this.dvrDuration = data.details.totalduration 370 this.dvrDuration = data.details.totalduration
371
369 this._duration = this.isLive ? Infinity : data.details.totalduration 372 this._duration = this.isLive ? Infinity : data.details.totalduration
370 }) 373 })
371 374
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'
20import { environment } from '../environments/environment' 21import { 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)