diff options
Diffstat (limited to 'client')
43 files changed, 511 insertions, 171 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/+plugin-pages/index.ts b/client/src/app/+plugin-pages/index.ts new file mode 100644 index 000000000..b988f13f6 --- /dev/null +++ b/client/src/app/+plugin-pages/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './plugin-pages-routing.module' | ||
2 | export * from './plugin-pages.component' | ||
3 | export * from './plugin-pages.module' | ||
diff --git a/client/src/app/+plugin-pages/plugin-pages-routing.module.ts b/client/src/app/+plugin-pages/plugin-pages-routing.module.ts new file mode 100644 index 000000000..b47a787e0 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages-routing.module.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RouterModule, Routes } from '@angular/router' | ||
3 | import { PluginPagesComponent } from './plugin-pages.component' | ||
4 | |||
5 | const pluginPagesRoutes: Routes = [ | ||
6 | { | ||
7 | path: '**', | ||
8 | component: PluginPagesComponent, | ||
9 | data: { | ||
10 | reloadOnSameNavigation: true | ||
11 | } | ||
12 | } | ||
13 | ] | ||
14 | |||
15 | @NgModule({ | ||
16 | imports: [ RouterModule.forChild(pluginPagesRoutes) ], | ||
17 | exports: [ RouterModule ] | ||
18 | }) | ||
19 | export class PluginPagesRoutingModule {} | ||
diff --git a/client/src/app/+plugin-pages/plugin-pages.component.html b/client/src/app/+plugin-pages/plugin-pages.component.html new file mode 100644 index 000000000..cf62d1bd7 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.component.html | |||
@@ -0,0 +1 @@ | |||
<div #root></div> | |||
diff --git a/client/src/app/+plugin-pages/plugin-pages.component.ts b/client/src/app/+plugin-pages/plugin-pages.component.ts new file mode 100644 index 000000000..5f294ee13 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.component.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { PluginService } from '@app/core' | ||
4 | |||
5 | @Component({ | ||
6 | templateUrl: './plugin-pages.component.html' | ||
7 | }) | ||
8 | export class PluginPagesComponent implements AfterViewInit { | ||
9 | @ViewChild('root') root: ElementRef | ||
10 | |||
11 | constructor ( | ||
12 | private route: ActivatedRoute, | ||
13 | private router: Router, | ||
14 | private pluginService: PluginService | ||
15 | ) { | ||
16 | |||
17 | } | ||
18 | |||
19 | ngAfterViewInit () { | ||
20 | const path = '/' + this.route.snapshot.url.map(u => u.path).join('/') | ||
21 | |||
22 | const registered = this.pluginService.getRegisteredClientRoute(path) | ||
23 | if (!registered) { | ||
24 | console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes()) | ||
25 | |||
26 | return this.router.navigate([ '/404' ], { skipLocationChange: true }) | ||
27 | } | ||
28 | |||
29 | registered.onMount({ rootEl: this.root.nativeElement }) | ||
30 | } | ||
31 | } | ||
diff --git a/client/src/app/+plugin-pages/plugin-pages.module.ts b/client/src/app/+plugin-pages/plugin-pages.module.ts new file mode 100644 index 000000000..86f86c752 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.module.ts | |||
@@ -0,0 +1,21 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { PluginPagesRoutingModule } from './plugin-pages-routing.module' | ||
3 | import { PluginPagesComponent } from './plugin-pages.component' | ||
4 | |||
5 | @NgModule({ | ||
6 | imports: [ | ||
7 | PluginPagesRoutingModule | ||
8 | ], | ||
9 | |||
10 | declarations: [ | ||
11 | PluginPagesComponent | ||
12 | ], | ||
13 | |||
14 | exports: [ | ||
15 | PluginPagesComponent | ||
16 | ], | ||
17 | |||
18 | providers: [ | ||
19 | ] | ||
20 | }) | ||
21 | export class PluginPagesModule { } | ||
diff --git a/client/src/app/+search/search-filters.component.html b/client/src/app/+search/search-filters.component.html index 4b87a2102..c4861e8c4 100644 --- a/client/src/app/+search/search-filters.component.html +++ b/client/src/app/+search/search-filters.component.html | |||
@@ -182,6 +182,31 @@ | |||
182 | > | 182 | > |
183 | </div> | 183 | </div> |
184 | 184 | ||
185 | <div class="form-group"> | ||
186 | <div class="radio-label label-container"> | ||
187 | <label i18n>Result types</label> | ||
188 | <button i18n class="reset-button reset-button-small" (click)="resetField('resultType')" *ngIf="advancedSearch.resultType !== undefined"> | ||
189 | Reset | ||
190 | </button> | ||
191 | </div> | ||
192 | |||
193 | <div class="peertube-radio-container"> | ||
194 | <input type="radio" name="resultType" id="resultTypeVideos" value="videos" [(ngModel)]="advancedSearch.resultType"> | ||
195 | <label i18n for="resultTypeVideos" class="radio">Videos</label> | ||
196 | </div> | ||
197 | |||
198 | <div class="peertube-radio-container"> | ||
199 | <input type="radio" name="resultType" id="resultTypeChannels" value="channels" [(ngModel)]="advancedSearch.resultType"> | ||
200 | <label i18n for="resultTypeChannels" class="radio">Channels</label> | ||
201 | </div> | ||
202 | |||
203 | <div class="peertube-radio-container"> | ||
204 | <input type="radio" name="resultType" id="resultTypePlaylists" value="playlists" [(ngModel)]="advancedSearch.resultType"> | ||
205 | <label i18n for="resultTypePlaylists" class="radio">Playlists</label> | ||
206 | </div> | ||
207 | |||
208 | </div> | ||
209 | |||
185 | <div class="form-group" *ngIf="isSearchTargetEnabled()"> | 210 | <div class="form-group" *ngIf="isSearchTargetEnabled()"> |
186 | <div class="radio-label label-container"> | 211 | <div class="radio-label label-container"> |
187 | <label i18n>Search target</label> | 212 | <label i18n>Search target</label> |
diff --git a/client/src/app/+search/search-filters.component.ts b/client/src/app/+search/search-filters.component.ts index 5972ba553..aaa4ecc5a 100644 --- a/client/src/app/+search/search-filters.component.ts +++ b/client/src/app/+search/search-filters.component.ts | |||
@@ -22,7 +22,6 @@ export class SearchFiltersComponent implements OnInit { | |||
22 | publishedDateRanges: FormOption[] = [] | 22 | publishedDateRanges: FormOption[] = [] |
23 | sorts: FormOption[] = [] | 23 | sorts: FormOption[] = [] |
24 | durationRanges: FormOption[] = [] | 24 | durationRanges: FormOption[] = [] |
25 | videoType: FormOption[] = [] | ||
26 | 25 | ||
27 | publishedDateRange: string | 26 | publishedDateRange: string |
28 | durationRange: string | 27 | durationRange: string |
@@ -54,17 +53,6 @@ export class SearchFiltersComponent implements OnInit { | |||
54 | } | 53 | } |
55 | ] | 54 | ] |
56 | 55 | ||
57 | this.videoType = [ | ||
58 | { | ||
59 | id: 'vod', | ||
60 | label: $localize`VOD videos` | ||
61 | }, | ||
62 | { | ||
63 | id: 'live', | ||
64 | label: $localize`Live videos` | ||
65 | } | ||
66 | ] | ||
67 | |||
68 | this.durationRanges = [ | 56 | this.durationRanges = [ |
69 | { | 57 | { |
70 | id: 'short', | 58 | id: 'short', |
diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts index fcf6ebbec..b9ec6dbcc 100644 --- a/client/src/app/+search/search.component.ts +++ b/client/src/app/+search/search.component.ts | |||
@@ -47,10 +47,6 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
47 | private subActivatedRoute: Subscription | 47 | private subActivatedRoute: Subscription |
48 | private isInitialLoad = false // set to false to show the search filters on first arrival | 48 | private isInitialLoad = false // set to false to show the search filters on first arrival |
49 | 49 | ||
50 | private channelsPerPage = 2 | ||
51 | private playlistsPerPage = 2 | ||
52 | private videosPerPage = 10 | ||
53 | |||
54 | private hasMoreResults = true | 50 | private hasMoreResults = true |
55 | private isSearching = false | 51 | private isSearching = false |
56 | 52 | ||
@@ -247,7 +243,6 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
247 | private resetPagination () { | 243 | private resetPagination () { |
248 | this.pagination.currentPage = 1 | 244 | this.pagination.currentPage = 1 |
249 | this.pagination.totalItems = null | 245 | this.pagination.totalItems = null |
250 | this.channelsPerPage = 2 | ||
251 | 246 | ||
252 | this.results = [] | 247 | this.results = [] |
253 | } | 248 | } |
@@ -272,7 +267,7 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
272 | private getVideosObs () { | 267 | private getVideosObs () { |
273 | const params = { | 268 | const params = { |
274 | search: this.currentSearch, | 269 | search: this.currentSearch, |
275 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.videosPerPage }), | 270 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: 10 }), |
276 | advancedSearch: this.advancedSearch | 271 | advancedSearch: this.advancedSearch |
277 | } | 272 | } |
278 | 273 | ||
@@ -288,7 +283,7 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
288 | private getVideoChannelObs () { | 283 | private getVideoChannelObs () { |
289 | const params = { | 284 | const params = { |
290 | search: this.currentSearch, | 285 | search: this.currentSearch, |
291 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }), | 286 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.buildChannelsPerPage() }), |
292 | advancedSearch: this.advancedSearch | 287 | advancedSearch: this.advancedSearch |
293 | } | 288 | } |
294 | 289 | ||
@@ -304,7 +299,7 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
304 | private getVideoPlaylistObs () { | 299 | private getVideoPlaylistObs () { |
305 | const params = { | 300 | const params = { |
306 | search: this.currentSearch, | 301 | search: this.currentSearch, |
307 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.playlistsPerPage }), | 302 | componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.buildPlaylistsPerPage() }), |
308 | advancedSearch: this.advancedSearch | 303 | advancedSearch: this.advancedSearch |
309 | } | 304 | } |
310 | 305 | ||
@@ -334,4 +329,16 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
334 | 329 | ||
335 | return undefined | 330 | return undefined |
336 | } | 331 | } |
332 | |||
333 | private buildChannelsPerPage () { | ||
334 | if (this.advancedSearch.resultType === 'channels') return 10 | ||
335 | |||
336 | return 2 | ||
337 | } | ||
338 | |||
339 | private buildPlaylistsPerPage () { | ||
340 | if (this.advancedSearch.resultType === 'playlists') return 10 | ||
341 | |||
342 | return 2 | ||
343 | } | ||
337 | } | 344 | } |
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/app-routing.module.ts b/client/src/app/app-routing.module.ts index 438cb6512..42328d83d 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts | |||
@@ -58,6 +58,12 @@ const routes: Routes = [ | |||
58 | }, | 58 | }, |
59 | 59 | ||
60 | { | 60 | { |
61 | path: 'p', | ||
62 | loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule), | ||
63 | canActivateChild: [ MetaGuard ] | ||
64 | }, | ||
65 | |||
66 | { | ||
61 | path: 'about', | 67 | path: 'about', |
62 | loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), | 68 | loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), |
63 | canActivateChild: [ MetaGuard ] | 69 | canActivateChild: [ MetaGuard ] |
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index 89391c2c5..fdbbd2d56 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -20,7 +20,8 @@ import { | |||
20 | PluginType, | 20 | PluginType, |
21 | PublicServerSetting, | 21 | PublicServerSetting, |
22 | RegisterClientFormFieldOptions, | 22 | RegisterClientFormFieldOptions, |
23 | RegisterClientSettingsScript, | 23 | RegisterClientSettingsScriptOptions, |
24 | RegisterClientRouteOptions, | ||
24 | RegisterClientVideoFieldOptions, | 25 | RegisterClientVideoFieldOptions, |
25 | ServerConfigPlugin | 26 | ServerConfigPlugin |
26 | } from '@shared/models' | 27 | } from '@shared/models' |
@@ -48,7 +49,8 @@ export class PluginService implements ClientHook { | |||
48 | private formFields: FormFields = { | 49 | private formFields: FormFields = { |
49 | video: [] | 50 | video: [] |
50 | } | 51 | } |
51 | private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {} | 52 | private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {} |
53 | private clientRoutes: { [ route: string ]: RegisterClientRouteOptions } = {} | ||
52 | 54 | ||
53 | private pluginsManager: PluginsManager | 55 | private pluginsManager: PluginsManager |
54 | 56 | ||
@@ -67,7 +69,8 @@ export class PluginService implements ClientHook { | |||
67 | this.pluginsManager = new PluginsManager({ | 69 | this.pluginsManager = new PluginsManager({ |
68 | peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), | 70 | peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), |
69 | onFormFields: this.onFormFields.bind(this), | 71 | onFormFields: this.onFormFields.bind(this), |
70 | onSettingsScripts: this.onSettingsScripts.bind(this) | 72 | onSettingsScripts: this.onSettingsScripts.bind(this), |
73 | onClientRoute: this.onClientRoute.bind(this) | ||
71 | }) | 74 | }) |
72 | } | 75 | } |
73 | 76 | ||
@@ -123,6 +126,14 @@ export class PluginService implements ClientHook { | |||
123 | return this.settingsScripts[npmName] | 126 | return this.settingsScripts[npmName] |
124 | } | 127 | } |
125 | 128 | ||
129 | getRegisteredClientRoute (route: string) { | ||
130 | return this.clientRoutes[route] | ||
131 | } | ||
132 | |||
133 | getAllRegisteredClientRoutes () { | ||
134 | return Object.keys(this.clientRoutes) | ||
135 | } | ||
136 | |||
126 | translateBy (npmName: string, toTranslate: string) { | 137 | translateBy (npmName: string, toTranslate: string) { |
127 | const helpers = this.helpers[npmName] | 138 | const helpers = this.helpers[npmName] |
128 | if (!helpers) { | 139 | if (!helpers) { |
@@ -140,12 +151,20 @@ export class PluginService implements ClientHook { | |||
140 | }) | 151 | }) |
141 | } | 152 | } |
142 | 153 | ||
143 | private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) { | 154 | private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) { |
144 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 155 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
145 | 156 | ||
146 | this.settingsScripts[npmName] = options | 157 | this.settingsScripts[npmName] = options |
147 | } | 158 | } |
148 | 159 | ||
160 | private onClientRoute (options: RegisterClientRouteOptions) { | ||
161 | const route = options.route.startsWith('/') | ||
162 | ? options.route | ||
163 | : `/${options.route}` | ||
164 | |||
165 | this.clientRoutes[route] = options | ||
166 | } | ||
167 | |||
149 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { | 168 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { |
150 | const { plugin } = pluginInfo | 169 | const { plugin } = pluginInfo |
151 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 170 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
@@ -161,6 +180,10 @@ export class PluginService implements ClientHook { | |||
161 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` | 180 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` |
162 | }, | 181 | }, |
163 | 182 | ||
183 | getBasePluginClientPath: () => { | ||
184 | return '/p' | ||
185 | }, | ||
186 | |||
164 | getSettings: () => { | 187 | getSettings: () => { |
165 | const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings' | 188 | const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings' |
166 | 189 | ||
diff --git a/client/src/app/core/routing/custom-reuse-strategy.ts b/client/src/app/core/routing/custom-reuse-strategy.ts index 1498e221f..5d3ad2e67 100644 --- a/client/src/app/core/routing/custom-reuse-strategy.ts +++ b/client/src/app/core/routing/custom-reuse-strategy.ts | |||
@@ -58,7 +58,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy { | |||
58 | 58 | ||
59 | // Reuse the route if we're going to and from the same route | 59 | // Reuse the route if we're going to and from the same route |
60 | shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { | 60 | shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { |
61 | return future.routeConfig === curr.routeConfig | 61 | return future.routeConfig === curr.routeConfig && future.routeConfig?.data?.reloadOnSameNavigation !== true |
62 | } | 62 | } |
63 | 63 | ||
64 | private gb () { | 64 | private gb () { |
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/app/shared/shared-search/advanced-search.model.ts b/client/src/app/shared/shared-search/advanced-search.model.ts index 2675c6135..724c4d834 100644 --- a/client/src/app/shared/shared-search/advanced-search.model.ts +++ b/client/src/app/shared/shared-search/advanced-search.model.ts | |||
@@ -8,6 +8,8 @@ import { | |||
8 | VideosSearchQuery | 8 | VideosSearchQuery |
9 | } from '@shared/models' | 9 | } from '@shared/models' |
10 | 10 | ||
11 | export type AdvancedSearchResultType = 'videos' | 'playlists' | 'channels' | ||
12 | |||
11 | export class AdvancedSearch { | 13 | export class AdvancedSearch { |
12 | startDate: string // ISO 8601 | 14 | startDate: string // ISO 8601 |
13 | endDate: string // ISO 8601 | 15 | endDate: string // ISO 8601 |
@@ -36,6 +38,7 @@ export class AdvancedSearch { | |||
36 | sort: string | 38 | sort: string |
37 | 39 | ||
38 | searchTarget: SearchTargetType | 40 | searchTarget: SearchTargetType |
41 | resultType: AdvancedSearchResultType | ||
39 | 42 | ||
40 | // Filters we don't want to count, because they are mandatory | 43 | // Filters we don't want to count, because they are mandatory |
41 | private silentFilters = new Set([ 'sort', 'searchTarget' ]) | 44 | private silentFilters = new Set([ 'sort', 'searchTarget' ]) |
@@ -61,6 +64,7 @@ export class AdvancedSearch { | |||
61 | durationMax?: string | 64 | durationMax?: string |
62 | sort?: string | 65 | sort?: string |
63 | searchTarget?: SearchTargetType | 66 | searchTarget?: SearchTargetType |
67 | resultType?: AdvancedSearchResultType | ||
64 | }) { | 68 | }) { |
65 | if (!options) return | 69 | if (!options) return |
66 | 70 | ||
@@ -84,6 +88,12 @@ export class AdvancedSearch { | |||
84 | 88 | ||
85 | this.searchTarget = options.searchTarget || undefined | 89 | this.searchTarget = options.searchTarget || undefined |
86 | 90 | ||
91 | this.resultType = options.resultType || undefined | ||
92 | |||
93 | if (!this.resultType && this.hasVideoFilter()) { | ||
94 | this.resultType = 'videos' | ||
95 | } | ||
96 | |||
87 | if (isNaN(this.durationMin)) this.durationMin = undefined | 97 | if (isNaN(this.durationMin)) this.durationMin = undefined |
88 | if (isNaN(this.durationMax)) this.durationMax = undefined | 98 | if (isNaN(this.durationMax)) this.durationMax = undefined |
89 | 99 | ||
@@ -137,7 +147,8 @@ export class AdvancedSearch { | |||
137 | isLive: this.isLive, | 147 | isLive: this.isLive, |
138 | host: this.host, | 148 | host: this.host, |
139 | sort: this.sort, | 149 | sort: this.sort, |
140 | searchTarget: this.searchTarget | 150 | searchTarget: this.searchTarget, |
151 | resultType: this.resultType | ||
141 | } | 152 | } |
142 | } | 153 | } |
143 | 154 | ||
@@ -199,4 +210,21 @@ export class AdvancedSearch { | |||
199 | 210 | ||
200 | return true | 211 | return true |
201 | } | 212 | } |
213 | |||
214 | private hasVideoFilter () { | ||
215 | return this.startDate !== undefined || | ||
216 | this.endDate !== undefined || | ||
217 | this.originallyPublishedStartDate !== undefined || | ||
218 | this.originallyPublishedEndDate !== undefined || | ||
219 | this.nsfw !== undefined !== undefined || | ||
220 | this.categoryOneOf !== undefined || | ||
221 | this.licenceOneOf !== undefined || | ||
222 | this.languageOneOf !== undefined || | ||
223 | this.tagsOneOf !== undefined || | ||
224 | this.tagsAllOf !== undefined || | ||
225 | this.durationMin !== undefined || | ||
226 | this.durationMax !== undefined || | ||
227 | this.host !== undefined || | ||
228 | this.isLive !== undefined | ||
229 | } | ||
202 | } | 230 | } |
diff --git a/client/src/app/shared/shared-search/search.service.ts b/client/src/app/shared/shared-search/search.service.ts index 71350c733..61acfb466 100644 --- a/client/src/app/shared/shared-search/search.service.ts +++ b/client/src/app/shared/shared-search/search.service.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Observable } from 'rxjs' | 1 | import { Observable, of } from 'rxjs' |
2 | import { catchError, map, switchMap } from 'rxjs/operators' | 2 | import { catchError, map, switchMap } 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' |
@@ -39,6 +39,10 @@ export class SearchService { | |||
39 | }): Observable<ResultList<Video>> { | 39 | }): Observable<ResultList<Video>> { |
40 | const { search, uuids, componentPagination, advancedSearch } = parameters | 40 | const { search, uuids, componentPagination, advancedSearch } = parameters |
41 | 41 | ||
42 | if (advancedSearch?.resultType !== undefined && advancedSearch.resultType !== 'videos') { | ||
43 | return of({ total: 0, data: [] }) | ||
44 | } | ||
45 | |||
42 | const url = SearchService.BASE_SEARCH_URL + 'videos' | 46 | const url = SearchService.BASE_SEARCH_URL + 'videos' |
43 | let pagination: RestPagination | 47 | let pagination: RestPagination |
44 | 48 | ||
@@ -73,6 +77,10 @@ export class SearchService { | |||
73 | }): Observable<ResultList<VideoChannel>> { | 77 | }): Observable<ResultList<VideoChannel>> { |
74 | const { search, advancedSearch, componentPagination, handles } = parameters | 78 | const { search, advancedSearch, componentPagination, handles } = parameters |
75 | 79 | ||
80 | if (advancedSearch?.resultType !== undefined && advancedSearch.resultType !== 'channels') { | ||
81 | return of({ total: 0, data: [] }) | ||
82 | } | ||
83 | |||
76 | const url = SearchService.BASE_SEARCH_URL + 'video-channels' | 84 | const url = SearchService.BASE_SEARCH_URL + 'video-channels' |
77 | 85 | ||
78 | let pagination: RestPagination | 86 | let pagination: RestPagination |
@@ -107,6 +115,10 @@ export class SearchService { | |||
107 | }): Observable<ResultList<VideoPlaylist>> { | 115 | }): Observable<ResultList<VideoPlaylist>> { |
108 | const { search, advancedSearch, componentPagination, uuids } = parameters | 116 | const { search, advancedSearch, componentPagination, uuids } = parameters |
109 | 117 | ||
118 | if (advancedSearch?.resultType !== undefined && advancedSearch.resultType !== 'playlists') { | ||
119 | return of({ total: 0, data: [] }) | ||
120 | } | ||
121 | |||
110 | const url = SearchService.BASE_SEARCH_URL + 'video-playlists' | 122 | const url = SearchService.BASE_SEARCH_URL + 'video-playlists' |
111 | 123 | ||
112 | let pagination: RestPagination | 124 | let pagination: RestPagination |
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..e574e75a3 100644 --- a/client/src/root-helpers/plugins-manager.ts +++ b/client/src/root-helpers/plugins-manager.ts | |||
@@ -13,8 +13,10 @@ import { | |||
13 | PluginType, | 13 | PluginType, |
14 | RegisterClientFormFieldOptions, | 14 | RegisterClientFormFieldOptions, |
15 | RegisterClientHookOptions, | 15 | RegisterClientHookOptions, |
16 | RegisterClientSettingsScript, | 16 | RegisterClientRouteOptions, |
17 | RegisterClientSettingsScriptOptions, | ||
17 | RegisterClientVideoFieldOptions, | 18 | RegisterClientVideoFieldOptions, |
19 | RegisteredExternalAuthConfig, | ||
18 | ServerConfigPlugin | 20 | ServerConfigPlugin |
19 | } from '../../../shared/models' | 21 | } from '../../../shared/models' |
20 | import { environment } from '../environments/environment' | 22 | import { environment } from '../environments/environment' |
@@ -36,7 +38,8 @@ type PluginInfo = { | |||
36 | 38 | ||
37 | type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers | 39 | type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers |
38 | type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void | 40 | type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void |
39 | type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) => void | 41 | type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) => void |
42 | type OnClientRoute = (options: RegisterClientRouteOptions) => void | ||
40 | 43 | ||
41 | const logger = debug('peertube:plugins') | 44 | const logger = debug('peertube:plugins') |
42 | 45 | ||
@@ -63,21 +66,29 @@ class PluginsManager { | |||
63 | private readonly peertubeHelpersFactory: PeertubeHelpersFactory | 66 | private readonly peertubeHelpersFactory: PeertubeHelpersFactory |
64 | private readonly onFormFields: OnFormFields | 67 | private readonly onFormFields: OnFormFields |
65 | private readonly onSettingsScripts: OnSettingsScripts | 68 | private readonly onSettingsScripts: OnSettingsScripts |
69 | private readonly onClientRoute: OnClientRoute | ||
66 | 70 | ||
67 | constructor (options: { | 71 | constructor (options: { |
68 | peertubeHelpersFactory: PeertubeHelpersFactory | 72 | peertubeHelpersFactory: PeertubeHelpersFactory |
69 | onFormFields?: OnFormFields | 73 | onFormFields?: OnFormFields |
70 | onSettingsScripts?: OnSettingsScripts | 74 | onSettingsScripts?: OnSettingsScripts |
75 | onClientRoute?: OnClientRoute | ||
71 | }) { | 76 | }) { |
72 | this.peertubeHelpersFactory = options.peertubeHelpersFactory | 77 | this.peertubeHelpersFactory = options.peertubeHelpersFactory |
73 | this.onFormFields = options.onFormFields | 78 | this.onFormFields = options.onFormFields |
74 | this.onSettingsScripts = options.onSettingsScripts | 79 | this.onSettingsScripts = options.onSettingsScripts |
80 | this.onClientRoute = options.onClientRoute | ||
75 | } | 81 | } |
76 | 82 | ||
77 | static getPluginPathPrefix (isTheme: boolean) { | 83 | static getPluginPathPrefix (isTheme: boolean) { |
78 | return isTheme ? '/themes' : '/plugins' | 84 | return isTheme ? '/themes' : '/plugins' |
79 | } | 85 | } |
80 | 86 | ||
87 | static getExternalAuthHref (auth: RegisteredExternalAuthConfig) { | ||
88 | return environment.apiUrl + `/plugins/${auth.name}/${auth.version}/auth/${auth.authName}` | ||
89 | |||
90 | } | ||
91 | |||
81 | loadPluginsList (config: HTMLServerConfig) { | 92 | loadPluginsList (config: HTMLServerConfig) { |
82 | for (const plugin of config.plugin.registered) { | 93 | for (const plugin of config.plugin.registered) { |
83 | this.addPlugin(plugin) | 94 | this.addPlugin(plugin) |
@@ -215,7 +226,7 @@ class PluginsManager { | |||
215 | return this.onFormFields(commonOptions, videoFormOptions) | 226 | return this.onFormFields(commonOptions, videoFormOptions) |
216 | } | 227 | } |
217 | 228 | ||
218 | const registerSettingsScript = (options: RegisterClientSettingsScript) => { | 229 | const registerSettingsScript = (options: RegisterClientSettingsScriptOptions) => { |
219 | if (!this.onSettingsScripts) { | 230 | if (!this.onSettingsScripts) { |
220 | throw new Error('Registering settings script is not supported') | 231 | throw new Error('Registering settings script is not supported') |
221 | } | 232 | } |
@@ -223,13 +234,29 @@ class PluginsManager { | |||
223 | return this.onSettingsScripts(pluginInfo, options) | 234 | return this.onSettingsScripts(pluginInfo, options) |
224 | } | 235 | } |
225 | 236 | ||
237 | const registerClientRoute = (options: RegisterClientRouteOptions) => { | ||
238 | if (!this.onClientRoute) { | ||
239 | throw new Error('Registering client route is not supported') | ||
240 | } | ||
241 | |||
242 | return this.onClientRoute(options) | ||
243 | } | ||
244 | |||
226 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) | 245 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) |
227 | 246 | ||
228 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | 247 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) |
229 | 248 | ||
230 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script | 249 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script |
231 | return dynamicImport(absURL) | 250 | return dynamicImport(absURL) |
232 | .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers })) | 251 | .then((script: ClientScriptModule) => { |
252 | return script.register({ | ||
253 | registerHook, | ||
254 | registerVideoField, | ||
255 | registerSettingsScript, | ||
256 | registerClientRoute, | ||
257 | peertubeHelpers | ||
258 | }) | ||
259 | }) | ||
233 | .then(() => this.sortHooksByPriority()) | 260 | .then(() => this.sortHooksByPriority()) |
234 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | 261 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) |
235 | } | 262 | } |
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); |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 9d1c6c443..874be580d 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -758,8 +758,8 @@ export class PeerTubeEmbed { | |||
758 | 758 | ||
759 | return { | 759 | return { |
760 | getBaseStaticRoute: unimplemented, | 760 | getBaseStaticRoute: unimplemented, |
761 | |||
762 | getBaseRouterRoute: unimplemented, | 761 | getBaseRouterRoute: unimplemented, |
762 | getBasePluginClientPath: unimplemented, | ||
763 | 763 | ||
764 | getSettings: unimplemented, | 764 | getSettings: unimplemented, |
765 | 765 | ||
diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts index 3415ef08f..73f82e781 100644 --- a/client/src/types/register-client-option.model.ts +++ b/client/src/types/register-client-option.model.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { | 1 | import { |
2 | RegisterClientFormFieldOptions, | 2 | RegisterClientFormFieldOptions, |
3 | RegisterClientHookOptions, | 3 | RegisterClientHookOptions, |
4 | RegisterClientSettingsScript, | 4 | RegisterClientRouteOptions, |
5 | RegisterClientSettingsScriptOptions, | ||
5 | RegisterClientVideoFieldOptions, | 6 | RegisterClientVideoFieldOptions, |
6 | ServerConfig | 7 | ServerConfig |
7 | } from '@shared/models' | 8 | } from '@shared/models' |
@@ -11,7 +12,9 @@ export type RegisterClientOptions = { | |||
11 | 12 | ||
12 | registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void | 13 | registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void |
13 | 14 | ||
14 | registerSettingsScript: (options: RegisterClientSettingsScript) => void | 15 | registerSettingsScript: (options: RegisterClientSettingsScriptOptions) => void |
16 | |||
17 | registerClientRoute: (options: RegisterClientRouteOptions) => void | ||
15 | 18 | ||
16 | peertubeHelpers: RegisterClientHelpers | 19 | peertubeHelpers: RegisterClientHelpers |
17 | } | 20 | } |
@@ -21,6 +24,8 @@ export type RegisterClientHelpers = { | |||
21 | 24 | ||
22 | getBaseRouterRoute: () => string | 25 | getBaseRouterRoute: () => string |
23 | 26 | ||
27 | getBasePluginClientPath: () => string | ||
28 | |||
24 | isLoggedIn: () => boolean | 29 | isLoggedIn: () => boolean |
25 | 30 | ||
26 | getAuthHeader: () => { 'Authorization': string } | undefined | 31 | getAuthHeader: () => { 'Authorization': string } | undefined |