diff options
Diffstat (limited to 'client/src/app')
331 files changed, 2788 insertions, 2721 deletions
diff --git a/client/src/app/+about/about-follows/about-follows.component.html b/client/src/app/+about/about-follows/about-follows.component.html index 6bc1d0448..f16f8bd71 100644 --- a/client/src/app/+about/about-follows/about-follows.component.html +++ b/client/src/app/+about/about-follows/about-follows.component.html | |||
@@ -1,5 +1,6 @@ | |||
1 | <div class="row"> | 1 | <div class="row"> |
2 | <h1 class="sr-only" i18n>Follows</h1> | 2 | <h1 class="visually-hidden" i18n>Follows</h1> |
3 | |||
3 | <div class="col-xl-6 col-md-12"> | 4 | <div class="col-xl-6 col-md-12"> |
4 | <h2 i18n class="subtitle">Follower instances ({{ followersPagination.totalItems }})</h2> | 5 | <h2 i18n class="subtitle">Follower instances ({{ followersPagination.totalItems }})</h2> |
5 | 6 | ||
diff --git a/client/src/app/+about/about-follows/about-follows.component.scss b/client/src/app/+about/about-follows/about-follows.component.scss index 80eb997e4..057d04ced 100644 --- a/client/src/app/+about/about-follows/about-follows.component.scss +++ b/client/src/app/+about/about-follows/about-follows.component.scss | |||
@@ -15,6 +15,7 @@ a { | |||
15 | 15 | ||
16 | .no-results { | 16 | .no-results { |
17 | justify-content: flex-start; | 17 | justify-content: flex-start; |
18 | align-items: flex-start; | ||
18 | } | 19 | } |
19 | 20 | ||
20 | .show-more { | 21 | .show-more { |
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 7f2a6aa77..b113df82f 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -8,9 +8,9 @@ | |||
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <div class="instance-badges" *ngIf="categories.length !== 0 || languages.length !== 0"> | 10 | <div class="instance-badges" *ngIf="categories.length !== 0 || languages.length !== 0"> |
11 | <span *ngFor="let category of categories" class="badge badge-primary category">{{ category }}</span> | 11 | <span *ngFor="let category of categories" class="pt-badge badge-primary">{{ category }}</span> |
12 | 12 | ||
13 | <span *ngFor="let language of languages" class="badge badge-secondary language">{{ language }}</span> | 13 | <span *ngFor="let language of languages" class="pt-badge badge-secondary">{{ language }}</span> |
14 | </div> | 14 | </div> |
15 | 15 | ||
16 | <div class="short-description"> | 16 | <div class="short-description"> |
@@ -204,7 +204,7 @@ | |||
204 | </div> | 204 | </div> |
205 | 205 | ||
206 | <div class="col-md-12 col-xl-6" myPluginSelector pluginSelectorId="about-instance-features"> | 206 | <div class="col-md-12 col-xl-6" myPluginSelector pluginSelectorId="about-instance-features"> |
207 | <h2 class="sr-only" i18n>FEATURES</h2> | 207 | <h2 class="visually-hidden" i18n>FEATURES</h2> |
208 | <my-instance-features-table></my-instance-features-table> | 208 | <my-instance-features-table></my-instance-features-table> |
209 | </div> | 209 | </div> |
210 | 210 | ||
diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss index 965c04b6c..c01e690d1 100644 --- a/client/src/app/+about/about-instance/about-instance.component.scss +++ b/client/src/app/+about/about-instance/about-instance.component.scss | |||
@@ -19,27 +19,19 @@ | |||
19 | } | 19 | } |
20 | 20 | ||
21 | .instance-badges { | 21 | .instance-badges { |
22 | font-size: 16px; | ||
23 | margin-bottom: 20px; | 22 | margin-bottom: 20px; |
24 | 23 | ||
25 | .badge { | 24 | .pt-badge { |
26 | @include margin-right(5px); | 25 | @include margin-right(5px); |
27 | |||
28 | font-size: 12px; | ||
29 | font-weight: $font-semibold; | ||
30 | |||
31 | &.category { | ||
32 | background-color: pvar(--mainColor); | ||
33 | } | ||
34 | } | 26 | } |
35 | } | 27 | } |
36 | 28 | ||
37 | .section-title { | 29 | .section-title { |
38 | font-weight: $font-semibold; | 30 | font-weight: $font-semibold; |
39 | font-size: 16px; | ||
40 | margin-bottom: 5px; | 31 | margin-bottom: 5px; |
41 | display: flex; | 32 | display: flex; |
42 | align-items: center; | 33 | align-items: center; |
34 | font-size: 1rem; | ||
43 | } | 35 | } |
44 | 36 | ||
45 | .middle-title { | 37 | .middle-title { |
@@ -51,7 +43,6 @@ | |||
51 | 43 | ||
52 | .block { | 44 | .block { |
53 | margin-bottom: 75px; | 45 | margin-bottom: 75px; |
54 | font-size: 15px; | ||
55 | } | 46 | } |
56 | 47 | ||
57 | .short-description { | 48 | .short-description { |
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.html b/client/src/app/+about/about-instance/contact-admin-modal.component.html index ed027af44..a14ba3a4f 100644 --- a/client/src/app/+about/about-instance/contact-admin-modal.component.html +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.html | |||
@@ -53,7 +53,7 @@ | |||
53 | </div> | 53 | </div> |
54 | </form> | 54 | </form> |
55 | 55 | ||
56 | <div *ngIf="!isContactFormEnabled()" class="alert alert-error" i18n>The contact form is not enabled on this instance.</div> | 56 | <div *ngIf="!isContactFormEnabled()" class="alert alert-danger" i18n>The contact form is not enabled on this instance.</div> |
57 | 57 | ||
58 | </div> | 58 | </div> |
59 | </ng-template> | 59 | </ng-template> |
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.scss b/client/src/app/+about/about-instance/contact-admin-modal.component.scss index e143a9dc6..6db6f13b3 100644 --- a/client/src/app/+about/about-instance/contact-admin-modal.component.scss +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.scss | |||
@@ -2,7 +2,6 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .modal-subtitle { | 4 | .modal-subtitle { |
5 | font-size: 16px; | ||
6 | line-height: 1rem; | 5 | line-height: 1rem; |
7 | margin-bottom: 0; | 6 | margin-bottom: 0; |
8 | } | 7 | } |
diff --git a/client/src/app/+about/about-peertube/about-peertube.component.html b/client/src/app/+about/about-peertube/about-peertube.component.html index 62689e3ae..f8700d9b6 100644 --- a/client/src/app/+about/about-peertube/about-peertube.component.html +++ b/client/src/app/+about/about-peertube/about-peertube.component.html | |||
@@ -11,12 +11,12 @@ | |||
11 | </p> | 11 | </p> |
12 | 12 | ||
13 | <p i18n> | 13 | <p i18n> |
14 | It is free and open-source software, under <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE">AGPLv3 | 14 | It is free and open-source software, under <a class="link-orange" href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE">AGPLv3 |
15 | licence</a>. | 15 | licence</a>. |
16 | </p> | 16 | </p> |
17 | 17 | ||
18 | <p i18n> | 18 | <p i18n> |
19 | For more information, please visit <a target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">joinpeertube.org</a>. | 19 | For more information, please visit <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">joinpeertube.org</a>. |
20 | </p> | 20 | </p> |
21 | </div> | 21 | </div> |
22 | 22 | ||
@@ -25,9 +25,8 @@ | |||
25 | <div class="card"> | 25 | <div class="card"> |
26 | <div class="card-body"> | 26 | <div class="card-body"> |
27 | <div class="card-title"> | 27 | <div class="card-title"> |
28 | <a i18n target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use-setup-account">Use PeerTube | 28 | <a i18n class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use-setup-account">Use PeerTube documentation</a> |
29 | documentation</a> | 29 | </div> |
30 | </div> | ||
31 | 30 | ||
32 | <div i18n class="card-text"> | 31 | <div i18n class="card-text"> |
33 | Discover how to setup your account, what is a channel, how to create a playlist and more! | 32 | Discover how to setup your account, what is a channel, how to create a playlist and more! |
@@ -38,9 +37,8 @@ | |||
38 | <div class="card"> | 37 | <div class="card"> |
39 | <div class="card-body"> | 38 | <div class="card-body"> |
40 | <div class="card-title"> | 39 | <div class="card-title"> |
41 | <a i18n target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use-third-party-application">PeerTube | 40 | <a i18n class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use-third-party-application">PeerTube Applications</a> |
42 | Applications</a> | 41 | </div> |
43 | </div> | ||
44 | 42 | ||
45 | <div i18n class="card-text"> | 43 | <div i18n class="card-text"> |
46 | Discover unofficial Android applications or browser addons! | 44 | Discover unofficial Android applications or browser addons! |
@@ -51,9 +49,8 @@ | |||
51 | <div class="card"> | 49 | <div class="card"> |
52 | <div class="card-body"> | 50 | <div class="card-body"> |
53 | <div class="card-title"> | 51 | <div class="card-title"> |
54 | <a i18n target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/contribute-getting-started">Contribute on | 52 | <a i18n class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/contribute-getting-started">Contribute on PeerTube</a> |
55 | PeerTube</a> | 53 | </div> |
56 | </div> | ||
57 | 54 | ||
58 | <div i18n class="card-text"> | 55 | <div i18n class="card-text"> |
59 | Want to help to improve PeerTube? You can translate the web interface, give your feedback or directly contribute to the code! | 56 | Want to help to improve PeerTube? You can translate the web interface, give your feedback or directly contribute to the code! |
@@ -116,7 +113,7 @@ | |||
116 | Web peers are not publicly accessible: because we use the websocket transport, the protocol is different from classic BitTorrent tracker. | 113 | Web peers are not publicly accessible: because we use the websocket transport, the protocol is different from classic BitTorrent tracker. |
117 | When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers | 114 | When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers |
118 | to forward the information to. | 115 | to forward the information to. |
119 | See <a href="https://github.com/yciabaud/webtorrent/blob/beps/bep_webrtc.rst">this document</a> for more information | 116 | See <a class="link-orange" href="https://github.com/yciabaud/webtorrent/blob/beps/bep_webrtc.rst">this document</a> for more information |
120 | </li> | 117 | </li> |
121 | </ul> | 118 | </ul> |
122 | 119 | ||
diff --git a/client/src/app/+about/about-peertube/about-peertube.component.scss b/client/src/app/+about/about-peertube/about-peertube.component.scss index 2a5ec08f5..a0d227a28 100644 --- a/client/src/app/+about/about-peertube/about-peertube.component.scss +++ b/client/src/app/+about/about-peertube/about-peertube.component.scss | |||
@@ -37,18 +37,6 @@ | |||
37 | .card { | 37 | .card { |
38 | margin: 30px; | 38 | margin: 30px; |
39 | flex-basis: 300px; | 39 | flex-basis: 300px; |
40 | font-size: 15px; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | .description, | ||
45 | .p2p-privacy, | ||
46 | my-about-peertube-contributors { | ||
47 | ::ng-deep { | ||
48 | p, | ||
49 | li { | ||
50 | font-size: 15px; | ||
51 | } | ||
52 | } | 40 | } |
53 | } | 41 | } |
54 | 42 | ||
@@ -70,7 +58,9 @@ my-about-peertube-contributors { | |||
70 | } | 58 | } |
71 | 59 | ||
72 | .card-title { | 60 | .card-title { |
73 | font-size: 1.25rem; | 61 | font-size: 1.1rem; |
62 | text-align: center; | ||
63 | margin-bottom: 1rem; | ||
74 | } | 64 | } |
75 | 65 | ||
76 | .p2p-privacy-title { | 66 | .p2p-privacy-title { |
diff --git a/client/src/app/+about/about.component.html b/client/src/app/+about/about.component.html index 63d429ebf..cf302e061 100644 --- a/client/src/app/+about/about.component.html +++ b/client/src/app/+about/about.component.html | |||
@@ -1,13 +1,10 @@ | |||
1 | <div class="row"> | 1 | <div> |
2 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }"> | 2 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }"> |
3 | <a myPluginSelector pluginSelectorId="about-menu-instance" i18n routerLink="instance" routerLinkActive="active" class="sub-menu-entry">Instance</a> | ||
3 | 4 | ||
4 | <div class="links"> | 5 | <a myPluginSelector pluginSelectorId="about-menu-peertube" i18n routerLink="peertube" routerLinkActive="active" class="sub-menu-entry">PeerTube</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 myPluginSelector pluginSelectorId="about-menu-peertube" i18n routerLink="peertube" routerLinkActive="active" class="title-page title-page-about">PeerTube</a> | 7 | <a myPluginSelector pluginSelectorId="about-menu-network" i18n routerLink="follows" routerLinkActive="active" class="sub-menu-entry">Network</a> |
8 | |||
9 | <a myPluginSelector pluginSelectorId="about-menu-network" i18n routerLink="follows" routerLinkActive="active" class="title-page title-page-about">Network</a> | ||
10 | </div> | ||
11 | </div> | 8 | </div> |
12 | 9 | ||
13 | <div class="margin-content" [ngClass]="{ 'offset-content': !isBroadcastMessageDisplayed }"> | 10 | <div class="margin-content" [ngClass]="{ 'offset-content': !isBroadcastMessageDisplayed }"> |
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html index 379c0443e..200d9415f 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <h1 class="sr-only" i18n>Video channels</h1> | 1 | <h1 class="visually-hidden" i18n>Video channels</h1> |
2 | 2 | ||
3 | <div class="margin-content"> | 3 | <div class="margin-content"> |
4 | 4 | ||
@@ -9,7 +9,7 @@ | |||
9 | 9 | ||
10 | <div class="channel-avatar-row"> | 10 | <div class="channel-avatar-row"> |
11 | <my-actor-avatar | 11 | <my-actor-avatar |
12 | [channel]="videoChannel" | 12 | [actor]="videoChannel" actorType="channel" |
13 | [internalHref]="getVideoChannelLink(videoChannel)" | 13 | [internalHref]="getVideoChannelLink(videoChannel)" |
14 | i18n-title | 14 | i18n-title |
15 | title="See this video channel" | 15 | title="See this video channel" |
@@ -23,10 +23,10 @@ | |||
23 | </h2> | 23 | </h2> |
24 | 24 | ||
25 | <div class="actor-counters"> | 25 | <div class="actor-counters"> |
26 | <div class="followers" i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> | 26 | <div class="followers" i18n>{videoChannel.followersCount, plural, =0 {No subscribers} =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> |
27 | 27 | ||
28 | <span class="videos-count" *ngIf="getTotalVideosOf(videoChannel) !== undefined" i18n> | 28 | <span class="videos-count" *ngIf="getTotalVideosOf(videoChannel) !== undefined" i18n> |
29 | {getTotalVideosOf(videoChannel), plural, =1 {1 videos} other {{{ getTotalVideosOf(videoChannel) }} videos}} | 29 | {getTotalVideosOf(videoChannel), plural, =0 {No videos} =1 {1 video} other {{{ getTotalVideosOf(videoChannel) }} videos}} |
30 | </span> | 30 | </span> |
31 | </div> | 31 | </div> |
32 | 32 | ||
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss index 30b8098be..832ddf973 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss | |||
@@ -65,7 +65,6 @@ | |||
65 | grid-row: 2; | 65 | grid-row: 2; |
66 | 66 | ||
67 | max-height: 80px; | 67 | max-height: 80px; |
68 | font-size: 16px; | ||
69 | } | 68 | } |
70 | } | 69 | } |
71 | 70 | ||
@@ -105,7 +104,6 @@ my-subscribe-button { | |||
105 | 104 | ||
106 | a { | 105 | a { |
107 | color: pvar(--mainColor); | 106 | color: pvar(--mainColor); |
108 | font-size: 16px; | ||
109 | font-weight: $font-semibold; | 107 | font-weight: $font-semibold; |
110 | } | 108 | } |
111 | } | 109 | } |
@@ -126,10 +124,6 @@ my-subscribe-button { | |||
126 | grid-row: 1 / 4; | 124 | grid-row: 1 / 4; |
127 | } | 125 | } |
128 | 126 | ||
129 | h2 { | ||
130 | font-size: 16px; | ||
131 | } | ||
132 | |||
133 | .actor-counters { | 127 | .actor-counters { |
134 | margin: 0; | 128 | margin: 0; |
135 | font-size: 13px; | 129 | font-size: 13px; |
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 8362e6b7e..226fa8f31 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <div class="account-info"> | 2 | <div class="account-info"> |
3 | 3 | ||
4 | <div class="account-avatar-row"> | 4 | <div class="account-avatar-row"> |
5 | <my-actor-avatar class="main-avatar" [account]="account"></my-actor-avatar> | 5 | <my-actor-avatar class="main-avatar" actorType="account" [actor]="account"></my-actor-avatar> |
6 | 6 | ||
7 | <div> | 7 | <div> |
8 | <div class="section-label" i18n>ACCOUNT</div> | 8 | <div class="section-label" i18n>ACCOUNT</div> |
@@ -18,7 +18,7 @@ | |||
18 | (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" | 18 | (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" |
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="pt-badge badge-danger" i18n>Banned</span> |
22 | 22 | ||
23 | <my-account-block-badges [account]="account"></my-account-block-badges> | 23 | <my-account-block-badges [account]="account"></my-account-block-badges> |
24 | </div> | 24 | </div> |
@@ -28,15 +28,15 @@ | |||
28 | <button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()" | 28 | <button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()" |
29 | class="btn btn-outline-secondary btn-sm copy-button" title="Copy account handle" i18n-title | 29 | class="btn btn-outline-secondary btn-sm copy-button" title="Copy account handle" i18n-title |
30 | > | 30 | > |
31 | <span class="glyphicon glyphicon-duplicate"></span> | 31 | <my-global-icon iconName="copy"></my-global-icon> |
32 | </button> | 32 | </button> |
33 | </div> | 33 | </div> |
34 | 34 | ||
35 | <div class="actor-counters"> | 35 | <div class="actor-counters"> |
36 | <span i18n>{naiveAggregatedSubscribers(), plural, =1 {1 subscriber} other {{{ naiveAggregatedSubscribers() }} subscribers}}</span> | 36 | <span i18n>{naiveAggregatedSubscribers(), plural, =0 {No subscribers} =1 {1 subscriber} other {{{ naiveAggregatedSubscribers() }} subscribers}}</span> |
37 | 37 | ||
38 | <span class="videos-count" *ngIf="accountVideosCount !== undefined" i18n> | 38 | <span class="videos-count" *ngIf="accountVideosCount !== undefined" i18n> |
39 | {accountVideosCount, plural, =1 {1 videos} other {{{ accountVideosCount }} videos}} | 39 | {accountVideosCount, plural, =0 {No videos} =1 {1 video} other {{{ accountVideosCount }} videos}} |
40 | </span> | 40 | </span> |
41 | </div> | 41 | </div> |
42 | </div> | 42 | </div> |
@@ -66,7 +66,7 @@ | |||
66 | 66 | ||
67 | <div class="links" [ngClass]="{ 'on-channel-page': isOnChannelPage() }"> | 67 | <div class="links" [ngClass]="{ 'on-channel-page': isOnChannelPage() }"> |
68 | <ng-template #linkTemplate let-item="item"> | 68 | <ng-template #linkTemplate let-item="item"> |
69 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> | 69 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="sub-menu-entry">{{ item.label }}</a> |
70 | </ng-template> | 70 | </ng-template> |
71 | 71 | ||
72 | <my-list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow> | 72 | <my-list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow> |
diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss index 5043b98c4..e5f86e61e 100644 --- a/client/src/app/+accounts/accounts.component.scss +++ b/client/src/app/+accounts/accounts.component.scss | |||
@@ -37,7 +37,13 @@ my-user-moderation-dropdown { | |||
37 | } | 37 | } |
38 | 38 | ||
39 | .copy-button { | 39 | .copy-button { |
40 | @include margin-left(3px); | ||
41 | |||
40 | border: 0; | 42 | border: 0; |
43 | |||
44 | my-global-icon { | ||
45 | width: 15px; | ||
46 | } | ||
41 | } | 47 | } |
42 | 48 | ||
43 | .account-info { | 49 | .account-info { |
@@ -93,6 +99,10 @@ my-user-moderation-dropdown { | |||
93 | } | 99 | } |
94 | } | 100 | } |
95 | 101 | ||
102 | .pt-badge { | ||
103 | @include margin-right(5px); | ||
104 | } | ||
105 | |||
96 | @media screen and (max-width: $small-view) { | 106 | @media screen and (max-width: $small-view) { |
97 | .root { | 107 | .root { |
98 | --myGlobalTopPadding: 45px; | 108 | --myGlobalTopPadding: 45px; |
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index 898325492..cf66b817a 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts | |||
@@ -30,8 +30,6 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
30 | links: ListOverflowItem[] = [] | 30 | links: ListOverflowItem[] = [] |
31 | hideMenu = false | 31 | hideMenu = false |
32 | 32 | ||
33 | accountFollowerTitle = '' | ||
34 | |||
35 | accountVideosCount: number | 33 | accountVideosCount: number |
36 | accountDescriptionHTML = '' | 34 | accountDescriptionHTML = '' |
37 | accountDescriptionExpanded = false | 35 | accountDescriptionExpanded = false |
@@ -121,12 +119,6 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
121 | this.notifier.success($localize`Username copied`) | 119 | this.notifier.success($localize`Username copied`) |
122 | } | 120 | } |
123 | 121 | ||
124 | subscribersDisplayFor (count: number) { | ||
125 | if (count === 1) return $localize`1 subscriber` | ||
126 | |||
127 | return $localize`${count} subscribers` | ||
128 | } | ||
129 | |||
130 | searchChanged (search: string) { | 122 | searchChanged (search: string) { |
131 | const queryParams = { search } | 123 | const queryParams = { search } |
132 | 124 | ||
@@ -150,8 +142,6 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
150 | } | 142 | } |
151 | 143 | ||
152 | private async onAccount (account: Account) { | 144 | private async onAccount (account: Account) { |
153 | this.accountFollowerTitle = $localize`${account.followersCount} direct account followers` | ||
154 | |||
155 | this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description) | 145 | this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description) |
156 | 146 | ||
157 | // After the markdown renderer to avoid layout changes | 147 | // After the markdown renderer to avoid layout changes |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html index 0ab80e5a9..a17b13fdf 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html | |||
@@ -1,15 +1,15 @@ | |||
1 | <ng-container [formGroup]="form"> | 1 | <ng-container [formGroup]="form"> |
2 | 2 | ||
3 | <div class="form-row mt-5"> <!-- cache grid --> | 3 | <div class="row mt-5"> <!-- cache grid --> |
4 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 4 | |
5 | <div class="col-12 col-lg-4 col-xl-3"> | ||
5 | <div i18n class="inner-form-title">CACHE</div> | 6 | <div i18n class="inner-form-title">CACHE</div> |
6 | <div i18n class="inner-form-description"> | 7 | <div i18n class="inner-form-description"> |
7 | Some files are not federated, and fetched when necessary. Define their caching policies. | 8 | Some files are not federated, and fetched when necessary. Define their caching policies. |
8 | </div> | 9 | </div> |
9 | </div> | 10 | </div> |
10 | 11 | ||
11 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 12 | <div class="col-12 col-lg-8 col-xl-9"> |
12 | |||
13 | <ng-container formGroupName="cache"> | 13 | <ng-container formGroupName="cache"> |
14 | <div class="form-group" formGroupName="previews"> | 14 | <div class="form-group" formGroupName="previews"> |
15 | <label i18n for="cachePreviewsSize">Number of previews to keep in cache</label> | 15 | <label i18n for="cachePreviewsSize">Number of previews to keep in cache</label> |
@@ -57,8 +57,8 @@ | |||
57 | </div> | 57 | </div> |
58 | </div> | 58 | </div> |
59 | 59 | ||
60 | <div class="form-row mt-4"> <!-- cache grid --> | 60 | <div class="row mt-4"> <!-- cache grid --> |
61 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 61 | <div class="col-12 col-lg-4 col-xl-3"> |
62 | <div class="anchor" id="customizations"></div> <!-- customizations anchor --> | 62 | <div class="anchor" id="customizations"></div> <!-- customizations anchor --> |
63 | <div i18n class="inner-form-title">CUSTOMIZATIONS</div> | 63 | <div i18n class="inner-form-title">CUSTOMIZATIONS</div> |
64 | <div i18n class="inner-form-description"> | 64 | <div i18n class="inner-form-description"> |
@@ -66,8 +66,7 @@ | |||
66 | </div> | 66 | </div> |
67 | </div> | 67 | </div> |
68 | 68 | ||
69 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 69 | <div class="col-12 col-lg-8 col-xl-9"> |
70 | |||
71 | <ng-container formGroupName="instance"> | 70 | <ng-container formGroupName="instance"> |
72 | <ng-container formGroupName="customizations"> | 71 | <ng-container formGroupName="customizations"> |
73 | <div class="form-group"> | 72 | <div class="form-group"> |
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 bae9d9775..7dfe5f5f9 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 | |||
@@ -1,13 +1,13 @@ | |||
1 | <ng-container [formGroup]="form"> | 1 | <ng-container [formGroup]="form"> |
2 | <div class="form-row mt-5"> <!-- appearance grid --> | 2 | <div class="row mt-5"> <!-- appearance grid --> |
3 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 3 | <div class="col-12 col-lg-4 col-xl-3"> |
4 | <div i18n class="inner-form-title">APPEARANCE</div> | 4 | <div i18n class="inner-form-title">APPEARANCE</div> |
5 | <div i18n class="inner-form-description"> | 5 | <div i18n class="inner-form-description"> |
6 | Use <a routerLink="/admin/plugins">plugins & themes</a> for more involved changes, or <a routerLink="/admin/config/edit-custom" fragment="advanced-configuration">add slight customizations</a>. | 6 | Use <a class="link-orange" routerLink="/admin/plugins">plugins & themes</a> for more involved changes, or add slight <a class="link-orange" routerLink="/admin/config/edit-custom" fragment="advanced-configuration">customizations</a>. |
7 | </div> | 7 | </div> |
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 10 | <div class="col-12 col-lg-8 col-xl-9"> |
11 | 11 | ||
12 | <ng-container formGroupName="theme"> | 12 | <ng-container formGroupName="theme"> |
13 | <div class="form-group"> | 13 | <div class="form-group"> |
@@ -15,9 +15,9 @@ | |||
15 | 15 | ||
16 | <div class="peertube-select-container"> | 16 | <div class="peertube-select-container"> |
17 | <select formControlName="default" id="themeDefault" class="form-control"> | 17 | <select formControlName="default" id="themeDefault" class="form-control"> |
18 | <option i18n value="default">default</option> | 18 | <option i18n value="default">{{ getDefaultThemeLabel() }}</option> |
19 | 19 | ||
20 | <option *ngFor="let theme of getAvailableThemes()" [value]="theme">{{ theme }}</option> | 20 | <option *ngFor="let theme of availableThemes" [value]="theme.id">{{ theme.label }}</option> |
21 | </select> | 21 | </select> |
22 | </div> | 22 | </div> |
23 | </div> | 23 | </div> |
@@ -88,15 +88,15 @@ | |||
88 | </div> | 88 | </div> |
89 | </div> | 89 | </div> |
90 | 90 | ||
91 | <div class="form-row mt-4"> <!-- broadcast grid --> | 91 | <div class="row mt-4"> <!-- broadcast grid --> |
92 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 92 | <div class="col-12 col-lg-4 col-xl-3"> |
93 | <div i18n class="inner-form-title">BROADCAST MESSAGE</div> | 93 | <div i18n class="inner-form-title">BROADCAST MESSAGE</div> |
94 | <div i18n class="inner-for-description"> | 94 | <div i18n class="inner-form-description"> |
95 | Display a message on your instance | 95 | Display a message on your instance |
96 | </div> | 96 | </div> |
97 | </div> | 97 | </div> |
98 | 98 | ||
99 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 99 | <div class="col-12 col-lg-8 col-xl-9"> |
100 | 100 | ||
101 | <ng-container formGroupName="broadcastMessage"> | 101 | <ng-container formGroupName="broadcastMessage"> |
102 | 102 | ||
@@ -132,8 +132,8 @@ | |||
132 | <label i18n for="broadcastMessageMessage">Message</label><my-help helpType="markdownText"></my-help> | 132 | <label i18n for="broadcastMessageMessage">Message</label><my-help helpType="markdownText"></my-help> |
133 | 133 | ||
134 | <my-markdown-textarea | 134 | <my-markdown-textarea |
135 | name="broadcastMessageMessage" formControlName="message" textareaMaxWidth="500px" | 135 | name="broadcastMessageMessage" formControlName="message" |
136 | [classes]="{ 'input-error': formErrors['broadcastMessage.message'] }" | 136 | [formError]="formErrors['broadcastMessage.message']" |
137 | ></my-markdown-textarea> | 137 | ></my-markdown-textarea> |
138 | 138 | ||
139 | <div *ngIf="formErrors.broadcastMessage.message" class="form-error">{{ formErrors.broadcastMessage.message }}</div> | 139 | <div *ngIf="formErrors.broadcastMessage.message" class="form-error">{{ formErrors.broadcastMessage.message }}</div> |
@@ -144,15 +144,15 @@ | |||
144 | </div> | 144 | </div> |
145 | </div> | 145 | </div> |
146 | 146 | ||
147 | <div class="form-row mt-4"> <!-- new users grid --> | 147 | <div class="row mt-4"> <!-- new users grid --> |
148 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 148 | <div class="col-12 col-lg-4 col-xl-3"> |
149 | <div i18n class="inner-form-title">NEW USERS</div> | 149 | <div i18n class="inner-form-title">NEW USERS</div> |
150 | <div i18n class="inner-for-description"> | 150 | <div i18n class="inner-form-description"> |
151 | Manage <a routerLink="/admin/users">users</a> to set their quota individually. | 151 | Manage <a class="link-orange" routerLink="/admin/users">users</a> to set their quota individually. |
152 | </div> | 152 | </div> |
153 | </div> | 153 | </div> |
154 | 154 | ||
155 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 155 | <div class="col-12 col-lg-8 col-xl-9"> |
156 | 156 | ||
157 | <ng-container formGroupName="signup"> | 157 | <ng-container formGroupName="signup"> |
158 | <div class="form-group"> | 158 | <div class="form-group"> |
@@ -163,7 +163,7 @@ | |||
163 | <ng-container ngProjectAs="description"> | 163 | <ng-container ngProjectAs="description"> |
164 | <span i18n>⚠️ This functionality requires a lot of attention and extra moderation.</span> | 164 | <span i18n>⚠️ This functionality requires a lot of attention and extra moderation.</span> |
165 | 165 | ||
166 | <div class="alert alert-info alert-signup" *ngIf="signupAlertMessage">{{ signupAlertMessage }}</div> | 166 | <div class="alert pt-alert-primary alert-signup" *ngIf="signupAlertMessage">{{ signupAlertMessage }}</div> |
167 | </ng-container> | 167 | </ng-container> |
168 | 168 | ||
169 | <ng-container ngProjectAs="extra"> | 169 | <ng-container ngProjectAs="extra"> |
@@ -185,7 +185,7 @@ | |||
185 | 185 | ||
186 | <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> | 186 | <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> |
187 | 187 | ||
188 | <small i18n *ngIf="hasUnlimitedSignup()" class="muted">Signup won't be limited to a fixed number of users.</small> | 188 | <small i18n *ngIf="hasUnlimitedSignup()" class="muted small">Signup won't be limited to a fixed number of users.</small> |
189 | </div> | 189 | </div> |
190 | 190 | ||
191 | <div [ngClass]="getDisabledSignupClass()" class="mt-3"> | 191 | <div [ngClass]="getDisabledSignupClass()" class="mt-3"> |
@@ -239,20 +239,20 @@ | |||
239 | </div> | 239 | </div> |
240 | </div> | 240 | </div> |
241 | 241 | ||
242 | <div class="form-row mt-4"> <!-- videos grid --> | 242 | <div class="row mt-4"> <!-- videos grid --> |
243 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 243 | <div class="col-12 col-lg-4 col-xl-3"> |
244 | <div i18n class="inner-form-title">VIDEOS</div> | 244 | <div i18n class="inner-form-title">VIDEOS</div> |
245 | </div> | 245 | </div> |
246 | 246 | ||
247 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 247 | <div class="col-12 col-lg-8 col-xl-9"> |
248 | 248 | ||
249 | <ng-container formGroupName="import"> | 249 | <ng-container formGroupName="import"> |
250 | 250 | ||
251 | <ng-container formGroupName="videos"> | 251 | <ng-container formGroupName="videos"> |
252 | 252 | ||
253 | <div class="form-group mt-4"> | 253 | <div class="form-group"> |
254 | <label i18n for="importConcurrency">Import jobs concurrency</label> | 254 | <label i18n for="importConcurrency">Import jobs concurrency</label> |
255 | <span i18n class="muted ml-1">allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart.</span> | 255 | <span i18n class="small muted ms-1">allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart.</span> |
256 | 256 | ||
257 | <div class="number-with-unit"> | 257 | <div class="number-with-unit"> |
258 | <input type="number" name="importConcurrency" formControlName="concurrency" /> | 258 | <input type="number" name="importConcurrency" formControlName="concurrency" /> |
@@ -268,7 +268,7 @@ | |||
268 | i18n-labelText labelText="Allow import with HTTP URL (e.g. YouTube)" | 268 | i18n-labelText labelText="Allow import with HTTP URL (e.g. YouTube)" |
269 | > | 269 | > |
270 | <ng-container ngProjectAs="description"> | 270 | <ng-container ngProjectAs="description"> |
271 | <span i18n>⚠️ If enabled, we recommend to use <a href="https://docs.joinpeertube.org/maintain-configuration?id=security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span> | 271 | <span i18n>⚠️ If enabled, we recommend to use <a class="link-orange" href="https://docs.joinpeertube.org/maintain-configuration?id=security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span> |
272 | </ng-container> | 272 | </ng-container> |
273 | </my-peertube-checkbox> | 273 | </my-peertube-checkbox> |
274 | </div> | 274 | </div> |
@@ -309,12 +309,12 @@ | |||
309 | </div> | 309 | </div> |
310 | </div> | 310 | </div> |
311 | 311 | ||
312 | <div class="form-row mt-4"> <!-- video channels grid --> | 312 | <div class="row mt-4"> <!-- video channels grid --> |
313 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 313 | <div class="col-12 col-lg-4 col-xl-3"> |
314 | <div i18n class="inner-form-title">VIDEO CHANNELS</div> | 314 | <div i18n class="inner-form-title">VIDEO CHANNELS</div> |
315 | </div> | 315 | </div> |
316 | 316 | ||
317 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 317 | <div class="col-12 col-lg-8 col-xl-9"> |
318 | <div class="form-group" formGroupName="videoChannels"> | 318 | <div class="form-group" formGroupName="videoChannels"> |
319 | <label i18n for="videoChannelsMaxPerUser">Max video channels per user</label> | 319 | <label i18n for="videoChannelsMaxPerUser">Max video channels per user</label> |
320 | 320 | ||
@@ -331,12 +331,12 @@ | |||
331 | </div> | 331 | </div> |
332 | </div> | 332 | </div> |
333 | 333 | ||
334 | <div class="form-row mt-4"> <!-- search grid --> | 334 | <div class="row mt-4"> <!-- search grid --> |
335 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 335 | <div class="col-12 col-lg-4 col-xl-3"> |
336 | <div i18n class="inner-form-title">SEARCH</div> | 336 | <div i18n class="inner-form-title">SEARCH</div> |
337 | </div> | 337 | </div> |
338 | 338 | ||
339 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 339 | <div class="col-12 col-lg-8 col-xl-9"> |
340 | 340 | ||
341 | <ng-container formGroupName="search"> | 341 | <ng-container formGroupName="search"> |
342 | <ng-container formGroupName="remoteUri"> | 342 | <ng-container formGroupName="remoteUri"> |
@@ -372,11 +372,11 @@ | |||
372 | i18n-labelText labelText="Enable global search" | 372 | i18n-labelText labelText="Enable global search" |
373 | > | 373 | > |
374 | <ng-container ngProjectAs="description"> | 374 | <ng-container ngProjectAs="description"> |
375 | <p i18n>⚠️ This functionality depends heavily on the moderation of instances followed by the search index you select.</p> | 375 | <div i18n>⚠️ This functionality depends heavily on the moderation of instances followed by the search index you select.</div> |
376 | 376 | ||
377 | <span i18n> | 377 | <div i18n> |
378 | You should only use moderated search indexes in production, or <a href="https://framagit.org/framasoft/peertube/search-index">host your own</a>. | 378 | You should only use moderated search indexes in production, or <a class="link-orange" href="https://framagit.org/framasoft/peertube/search-index">host your own</a>. |
379 | </span> | 379 | </div> |
380 | </ng-container> | 380 | </ng-container> |
381 | 381 | ||
382 | <ng-container ngProjectAs="extra"> | 382 | <ng-container ngProjectAs="extra"> |
@@ -420,15 +420,15 @@ | |||
420 | </div> | 420 | </div> |
421 | </div> | 421 | </div> |
422 | 422 | ||
423 | <div class="form-row mt-4"> <!-- federation grid --> | 423 | <div class="row mt-4"> <!-- federation grid --> |
424 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 424 | <div class="col-12 col-lg-4 col-xl-3"> |
425 | <div i18n class="inner-form-title">FEDERATION</div> | 425 | <div i18n class="inner-form-title">FEDERATION</div> |
426 | <div i18n class="inner-form-description"> | 426 | <div i18n class="inner-form-description"> |
427 | Manage <a routerLink="/admin/follows">relations</a> with other instances. | 427 | Manage <a class="link-orange" routerLink="/admin/follows">relations</a> with other instances. |
428 | </div> | 428 | </div> |
429 | </div> | 429 | </div> |
430 | 430 | ||
431 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 431 | <div class="col-12 col-lg-8 col-xl-9"> |
432 | 432 | ||
433 | <ng-container formGroupName="followers"> | 433 | <ng-container formGroupName="followers"> |
434 | <ng-container formGroupName="instance"> | 434 | <ng-container formGroupName="instance"> |
@@ -472,10 +472,10 @@ | |||
472 | i18n-labelText labelText="Automatically follow instances of a public index" | 472 | i18n-labelText labelText="Automatically follow instances of a public index" |
473 | > | 473 | > |
474 | <ng-container ngProjectAs="description"> | 474 | <ng-container ngProjectAs="description"> |
475 | <p i18n>⚠️ This functionality requires a lot of attention and extra moderation.</p> | 475 | <div i18n>⚠️ This functionality requires a lot of attention and extra moderation.</div> |
476 | 476 | ||
477 | <span i18n> | 477 | <span i18n> |
478 | See <a href="https://docs.joinpeertube.org/admin-following-instances?id=automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL | 478 | See <a class="link-orange" href="https://docs.joinpeertube.org/admin-following-instances?id=automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL |
479 | </span> | 479 | </span> |
480 | </ng-container> | 480 | </ng-container> |
481 | 481 | ||
@@ -499,12 +499,12 @@ | |||
499 | </div> | 499 | </div> |
500 | </div> | 500 | </div> |
501 | 501 | ||
502 | <div class="form-row mt-4"> <!-- administrators grid --> | 502 | <div class="row mt-4"> <!-- administrators grid --> |
503 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 503 | <div class="col-12 col-lg-4 col-xl-3"> |
504 | <div i18n class="inner-form-title">ADMINISTRATORS</div> | 504 | <div i18n class="inner-form-title">ADMINISTRATORS</div> |
505 | </div> | 505 | </div> |
506 | 506 | ||
507 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 507 | <div class="col-12 col-lg-8 col-xl-9"> |
508 | 508 | ||
509 | <div class="form-group" formGroupName="admin"> | 509 | <div class="form-group" formGroupName="admin"> |
510 | <label i18n for="adminEmail">Admin email</label> | 510 | <label i18n for="adminEmail">Admin email</label> |
@@ -527,8 +527,8 @@ | |||
527 | </div> | 527 | </div> |
528 | </div> | 528 | </div> |
529 | 529 | ||
530 | <div class="form-row mt-4"> <!-- Twitter grid --> | 530 | <div class="row mt-4"> <!-- Twitter grid --> |
531 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 531 | <div class="col-12 col-lg-4 col-xl-3"> |
532 | <div i18n class="inner-form-title">TWITTER</div> | 532 | <div i18n class="inner-form-title">TWITTER</div> |
533 | <div i18n class="inner-form-description"> | 533 | <div i18n class="inner-form-description"> |
534 | Provide the Twitter account representing your instance to improve link previews. | 534 | Provide the Twitter account representing your instance to improve link previews. |
@@ -536,7 +536,7 @@ | |||
536 | </div> | 536 | </div> |
537 | </div> | 537 | </div> |
538 | 538 | ||
539 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 539 | <div class="col-12 col-lg-8 col-xl-9"> |
540 | 540 | ||
541 | <ng-container formGroupName="services"> | 541 | <ng-container formGroupName="services"> |
542 | <ng-container formGroupName="twitter"> | 542 | <ng-container formGroupName="twitter"> |
@@ -563,7 +563,7 @@ | |||
563 | If your instance is explicitly allowed by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | 563 | If your instance is explicitly allowed by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> |
564 | If the instance is not, we use an image link card that will redirect to your PeerTube instance.<br /><br /> | 564 | If the instance is not, we use an image link card that will redirect to your PeerTube instance.<br /><br /> |
565 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/w/blabla) on | 565 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/w/blabla) on |
566 | <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> | 566 | <a class="link-orange" target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> |
567 | to see if you instance is allowed. | 567 | to see if you instance is allowed. |
568 | </ng-container> | 568 | </ng-container> |
569 | </ng-template> | 569 | </ng-template> |
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 81457bd36..56227d11c 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 | |||
@@ -2,7 +2,7 @@ import { pairwise } from 'rxjs/operators' | |||
2 | import { SelectOptionsItem } from 'src/types/select-options-item.model' | 2 | import { SelectOptionsItem } from 'src/types/select-options-item.model' |
3 | import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core' | 3 | import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core' |
4 | import { FormGroup } from '@angular/forms' | 4 | import { FormGroup } from '@angular/forms' |
5 | import { MenuService } from '@app/core' | 5 | import { MenuService, ThemeService } from '@app/core' |
6 | import { HTMLServerConfig } from '@shared/models' | 6 | import { HTMLServerConfig } from '@shared/models' |
7 | import { ConfigService } from '../shared/config.service' | 7 | import { ConfigService } from '../shared/config.service' |
8 | 8 | ||
@@ -19,15 +19,19 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges { | |||
19 | 19 | ||
20 | signupAlertMessage: string | 20 | signupAlertMessage: string |
21 | defaultLandingPageOptions: SelectOptionsItem[] = [] | 21 | defaultLandingPageOptions: SelectOptionsItem[] = [] |
22 | availableThemes: SelectOptionsItem[] | ||
22 | 23 | ||
23 | constructor ( | 24 | constructor ( |
24 | private configService: ConfigService, | 25 | private configService: ConfigService, |
25 | private menuService: MenuService | 26 | private menuService: MenuService, |
27 | private themeService: ThemeService | ||
26 | ) { } | 28 | ) { } |
27 | 29 | ||
28 | ngOnInit () { | 30 | ngOnInit () { |
29 | this.buildLandingPageOptions() | 31 | this.buildLandingPageOptions() |
30 | this.checkSignupField() | 32 | this.checkSignupField() |
33 | |||
34 | this.availableThemes = this.themeService.buildAvailableThemes() | ||
31 | } | 35 | } |
32 | 36 | ||
33 | ngOnChanges (changes: SimpleChanges) { | 37 | ngOnChanges (changes: SimpleChanges) { |
@@ -48,11 +52,6 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges { | |||
48 | return this.configService.videoQuotaDailyOptions | 52 | return this.configService.videoQuotaDailyOptions |
49 | } | 53 | } |
50 | 54 | ||
51 | getAvailableThemes () { | ||
52 | return this.serverConfig.theme.registered | ||
53 | .map(t => t.name) | ||
54 | } | ||
55 | |||
56 | doesTrendingVideosAlgorithmsEnabledInclude (algorithm: string) { | 55 | doesTrendingVideosAlgorithmsEnabledInclude (algorithm: string) { |
57 | const enabled = this.form.value['trending']['videos']['algorithms']['enabled'] | 56 | const enabled = this.form.value['trending']['videos']['algorithms']['enabled'] |
58 | if (!Array.isArray(enabled)) return false | 57 | if (!Array.isArray(enabled)) return false |
@@ -94,6 +93,10 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges { | |||
94 | })) | 93 | })) |
95 | } | 94 | } |
96 | 95 | ||
96 | getDefaultThemeLabel () { | ||
97 | return this.themeService.getDefaultThemeLabel() | ||
98 | } | ||
99 | |||
97 | private checkSignupField () { | 100 | private checkSignupField () { |
98 | const signupControl = this.form.get('signup.enabled') | 101 | const signupControl = this.form.get('signup.enabled') |
99 | 102 | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts b/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts index 9b55cb43c..96f5b830e 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { FormGroup } from '@angular/forms' | 2 | import { FormGroup } from '@angular/forms' |
3 | import { prepareIcu } from '@app/helpers' | ||
3 | 4 | ||
4 | export type ResolutionOption = { | 5 | export type ResolutionOption = { |
5 | id: string | 6 | id: string |
@@ -86,9 +87,10 @@ export class EditConfigurationService { | |||
86 | return { | 87 | return { |
87 | value, | 88 | value, |
88 | atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible | 89 | atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible |
89 | unit: value > 1 | 90 | unit: prepareIcu($localize`{value, plural, =1 {thread} other {threads}}`)( |
90 | ? $localize`threads` | 91 | { value }, |
91 | : $localize`thread` | 92 | $localize`threads` |
93 | ) | ||
92 | } | 94 | } |
93 | } | 95 | } |
94 | } | 96 | } |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index 8fef39b79..cc8e699f3 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <h1 class="sr-only" i18n>Configuration</h1> | 1 | <h1 class="visually-hidden" i18n>Configuration</h1> |
2 | 2 | ||
3 | <div class="alert alert-warning" *ngIf="!isUpdateAllowed()" i18n> | 3 | <div class="alert alert-warning" *ngIf="!isUpdateAllowed()" i18n> |
4 | Updating instance configuration from the web interface is disabled by the system administrator. | 4 | Updating instance configuration from the web interface is disabled by the system administrator. |
@@ -64,7 +64,7 @@ | |||
64 | 64 | ||
65 | <div [ngbNavOutlet]="nav"></div> | 65 | <div [ngbNavOutlet]="nav"></div> |
66 | 66 | ||
67 | <div class="form-row mt-4"> <!-- submit placement block --> | 67 | <div class="row mt-4"> <!-- submit placement block --> |
68 | <div class="col-md-7 col-xl-5"></div> | 68 | <div class="col-md-7 col-xl-5"></div> |
69 | <div class="col-md-5 col-xl-5"> | 69 | <div class="col-md-5 col-xl-5"> |
70 | 70 | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss index 0458d257f..df523cedc 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -3,21 +3,25 @@ | |||
3 | 3 | ||
4 | $form-base-input-width: 340px; | 4 | $form-base-input-width: 340px; |
5 | 5 | ||
6 | label { | ||
7 | font-weight: $font-regular; | ||
8 | font-size: 100%; | ||
9 | } | ||
10 | |||
11 | form { | 6 | form { |
12 | padding-bottom: 1.5rem; | 7 | padding-bottom: 1.5rem; |
13 | } | 8 | } |
14 | 9 | ||
15 | input[type=text] { | 10 | my-markdown-textarea { |
16 | @include peertube-input-text($form-base-input-width); | 11 | display: block; |
12 | max-width: 500px; | ||
13 | } | ||
17 | 14 | ||
15 | .homepage my-markdown-textarea { | ||
18 | display: block; | 16 | display: block; |
17 | max-width: 90%; | ||
18 | |||
19 | ::ng-deep textarea { | ||
20 | height: 300px !important; | ||
21 | } | ||
19 | } | 22 | } |
20 | 23 | ||
24 | input[type=text], | ||
21 | input[type=number] { | 25 | input[type=number] { |
22 | @include peertube-input-text($form-base-input-width); | 26 | @include peertube-input-text($form-base-input-width); |
23 | 27 | ||
@@ -30,7 +34,7 @@ input[type=number] { | |||
30 | 34 | ||
31 | input[type=number] + span { | 35 | input[type=number] + span { |
32 | position: absolute; | 36 | position: absolute; |
33 | top: 5px; | 37 | top: 0.2em; |
34 | right: 2.5rem; | 38 | right: 2.5rem; |
35 | } | 39 | } |
36 | 40 | ||
@@ -74,6 +78,10 @@ input[type=submit] { | |||
74 | @include settings-big-title; | 78 | @include settings-big-title; |
75 | } | 79 | } |
76 | 80 | ||
81 | .inner-form-description { | ||
82 | font-size: 15px; | ||
83 | } | ||
84 | |||
77 | textarea { | 85 | textarea { |
78 | @include peertube-textarea(500px, 150px); | 86 | @include peertube-textarea(500px, 150px); |
79 | 87 | ||
@@ -88,6 +96,7 @@ textarea { | |||
88 | .label-small-info { | 96 | .label-small-info { |
89 | font-style: italic; | 97 | font-style: italic; |
90 | margin-bottom: 10px; | 98 | margin-bottom: 10px; |
99 | font-size: 14px; | ||
91 | } | 100 | } |
92 | 101 | ||
93 | .disabled-checkbox-extra { | 102 | .disabled-checkbox-extra { |
@@ -102,11 +111,6 @@ input[disabled] { | |||
102 | opacity: 0.5; | 111 | opacity: 0.5; |
103 | } | 112 | } |
104 | 113 | ||
105 | |||
106 | .form-group-right { | ||
107 | padding-top: 2px; | ||
108 | } | ||
109 | |||
110 | ngb-tabset:not(.previews) ::ng-deep { | 114 | ngb-tabset:not(.previews) ::ng-deep { |
111 | .nav-link { | 115 | .nav-link { |
112 | font-size: 105%; | 116 | font-size: 105%; |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html b/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html index 2286a5a1a..5339240bb 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | <ng-container formGroupName="instanceCustomHomepage"> | 3 | <ng-container formGroupName="instanceCustomHomepage"> |
4 | 4 | ||
5 | <div class="form-row mt-5"> <!-- homepage grid --> | 5 | <div class="homepage row mt-5"> <!-- homepage grid --> |
6 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 6 | <div class="col-12 col-lg-4 col-xl-3"> |
7 | <div i18n class="inner-form-title">INSTANCE HOMEPAGE</div> | 7 | <div i18n class="inner-form-title">INSTANCE HOMEPAGE</div> |
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 10 | <div class="col-12 col-lg-8 col-xl-9"> |
11 | 11 | ||
12 | <div class="form-group"> | 12 | <div class="form-group"> |
13 | <label i18n for="instanceCustomHomepageContent">Homepage</label> | 13 | <label i18n for="instanceCustomHomepageContent">Homepage</label> |
@@ -16,9 +16,9 @@ | |||
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <my-markdown-textarea | 18 | <my-markdown-textarea |
19 | name="instanceCustomHomepageContent" formControlName="content" textareaMaxWidth="90%" textareaHeight="300px" | 19 | name="instanceCustomHomepageContent" formControlName="content" |
20 | [customMarkdownRenderer]="getCustomMarkdownRenderer()" | 20 | [customMarkdownRenderer]="getCustomMarkdownRenderer()" |
21 | [classes]="{ 'input-error': formErrors['instanceCustomHomepage.content'] }" | 21 | [formError]="formErrors['instanceCustomHomepage.content']" |
22 | ></my-markdown-textarea> | 22 | ></my-markdown-textarea> |
23 | 23 | ||
24 | <div *ngIf="formErrors.instanceCustomHomepage.content" class="form-error">{{ formErrors.instanceCustomHomepage.content }}</div> | 24 | <div *ngIf="formErrors.instanceCustomHomepage.content" class="form-error">{{ formErrors.instanceCustomHomepage.content }}</div> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html index d806616bd..b54733327 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html | |||
@@ -2,13 +2,12 @@ | |||
2 | 2 | ||
3 | <ng-container formGroupName="instance"> | 3 | <ng-container formGroupName="instance"> |
4 | 4 | ||
5 | <div class="form-row mt-5"> <!-- instance grid --> | 5 | <div class="row mt-5"> <!-- instance grid --> |
6 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 6 | <div class="col-12 col-lg-4 col-xl-3"> |
7 | <div i18n class="inner-form-title">INSTANCE</div> | 7 | <div i18n class="inner-form-title">INSTANCE</div> |
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 10 | <div class="col-12 col-lg-8 col-xl-9"> |
11 | |||
12 | <div class="form-group"> | 11 | <div class="form-group"> |
13 | <label i18n for="instanceName">Name</label> | 12 | <label i18n for="instanceName">Name</label> |
14 | 13 | ||
@@ -38,12 +37,10 @@ | |||
38 | </div> | 37 | </div> |
39 | 38 | ||
40 | <my-markdown-textarea | 39 | <my-markdown-textarea |
41 | name="instanceDescription" formControlName="description" textareaMaxWidth="500px" | 40 | name="instanceDescription" formControlName="description" |
42 | [customMarkdownRenderer]="getCustomMarkdownRenderer()" | 41 | [customMarkdownRenderer]="getCustomMarkdownRenderer()" |
43 | [classes]="{ 'input-error': formErrors['instance.description'] }" | 42 | [formError]="formErrors['instance.description']" |
44 | ></my-markdown-textarea> | 43 | ></my-markdown-textarea> |
45 | |||
46 | <div *ngIf="formErrors.instance.description" class="form-error">{{ formErrors.instance.description }}</div> | ||
47 | </div> | 44 | </div> |
48 | 45 | ||
49 | <div class="form-group"> | 46 | <div class="form-group"> |
@@ -77,16 +74,15 @@ | |||
77 | </div> | 74 | </div> |
78 | </div> | 75 | </div> |
79 | 76 | ||
80 | <div class="form-row mt-4"> <!-- moderation & nsfw grid --> | 77 | <div class="row mt-4"> <!-- moderation & nsfw grid --> |
81 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 78 | <div class="col-12 col-lg-4 col-xl-3"> |
82 | <div i18n class="inner-form-title">MODERATION & NSFW</div> | 79 | <div i18n class="inner-form-title">MODERATION & NSFW</div> |
83 | <div i18n class="inner-for-description"> | 80 | <div i18row="inner-form-description"> |
84 | Manage <a routerLink="/admin/users">users</a> to build a moderation team. | 81 | Manage <a class="link-orange" routerLink="/admin/users">users</a> to build a moderation team. |
85 | </div> | 82 | </div> |
86 | </div> | 83 | </div> |
87 | 84 | ||
88 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 85 | <div class="col-12 col-lg-8 col-xl-9"> |
89 | |||
90 | <div class="form-group"> | 86 | <div class="form-group"> |
91 | <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW"> | 87 | <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW"> |
92 | <ng-template ptTemplate="label"> | 88 | <ng-template ptTemplate="label"> |
@@ -129,22 +125,18 @@ | |||
129 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 125 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> |
130 | 126 | ||
131 | <my-markdown-textarea | 127 | <my-markdown-textarea |
132 | name="instanceTerms" formControlName="terms" textareaMaxWidth="500px" | 128 | name="instanceTerms" formControlName="terms" |
133 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | 129 | [formError]="formErrors['instance.terms']" |
134 | ></my-markdown-textarea> | 130 | ></my-markdown-textarea> |
135 | |||
136 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | ||
137 | </div> | 131 | </div> |
138 | 132 | ||
139 | <div class="form-group"> | 133 | <div class="form-group"> |
140 | <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help> | 134 | <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help> |
141 | 135 | ||
142 | <my-markdown-textarea | 136 | <my-markdown-textarea |
143 | name="instanceCodeOfConduct" formControlName="codeOfConduct" textareaMaxWidth="500px" | 137 | name="instanceCodeOfConduct" formControlName="codeOfConduct" |
144 | [ngClass]="{ 'input-error': formErrors['instance.codeOfConduct'] }" | 138 | [formError]="formErrors['instance.codeOfConduct']" |
145 | ></my-markdown-textarea> | 139 | ></my-markdown-textarea> |
146 | |||
147 | <div *ngIf="formErrors.instance.codeOfConduct" class="form-error">{{ formErrors.instance.codeOfConduct }}</div> | ||
148 | </div> | 140 | </div> |
149 | 141 | ||
150 | <div class="form-group"> | 142 | <div class="form-group"> |
@@ -152,33 +144,29 @@ | |||
152 | <div i18n class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div> | 144 | <div i18n class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div> |
153 | 145 | ||
154 | <my-markdown-textarea | 146 | <my-markdown-textarea |
155 | name="instanceModerationInformation" formControlName="moderationInformation" textareaMaxWidth="500px" | 147 | name="instanceModerationInformation" formControlName="moderationInformation" |
156 | [ngClass]="{ 'input-error': formErrors['instance.moderationInformation'] }" | 148 | [formError]="formErrors['instance.moderationInformation']" |
157 | ></my-markdown-textarea> | 149 | ></my-markdown-textarea> |
158 | |||
159 | <div *ngIf="formErrors.instance.moderationInformation" class="form-error">{{ formErrors.instance.moderationInformation }}</div> | ||
160 | </div> | 150 | </div> |
161 | 151 | ||
162 | </div> | 152 | </div> |
163 | </div> | 153 | </div> |
164 | 154 | ||
165 | <div class="form-row mt-4"> <!-- you and your instance grid --> | 155 | <div class="row mt-4"> <!-- you and your instance grid --> |
166 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 156 | <div class="col-12 col-lg-4 col-xl-3"> |
167 | <div i18n class="inner-form-title">YOU AND YOUR INSTANCE</div> | 157 | <div i18n class="inner-form-title">YOU AND YOUR INSTANCE</div> |
168 | </div> | 158 | </div> |
169 | 159 | ||
170 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 160 | <div class="col-12 col-lg-8 col-xl-9"> |
171 | 161 | ||
172 | <div class="form-group"> | 162 | <div class="form-group"> |
173 | <label i18n for="instanceAdministrator">Who is behind the instance?</label><my-help helpType="markdownText"></my-help> | 163 | <label i18n for="instanceAdministrator">Who is behind the instance?</label><my-help helpType="markdownText"></my-help> |
174 | <div i18n class="label-small-info">A single person? A non-profit? A company?</div> | 164 | <div i18n class="label-small-info">A single person? A non-profit? A company?</div> |
175 | 165 | ||
176 | <my-markdown-textarea | 166 | <my-markdown-textarea |
177 | name="instanceAdministrator" formControlName="administrator" textareaMaxWidth="500px" | 167 | name="instanceAdministrator" formControlName="administrator" |
178 | [classes]="{ 'input-error': formErrors['instance.administrator'] }" | 168 | [formError]="formErrors['instance.administrator']" |
179 | ></my-markdown-textarea> | 169 | ></my-markdown-textarea> |
180 | |||
181 | <div *ngIf="formErrors.instance.administrator" class="form-error">{{ formErrors.instance.administrator }}</div> | ||
182 | </div> | 170 | </div> |
183 | 171 | ||
184 | <div class="form-group"> | 172 | <div class="form-group"> |
@@ -186,11 +174,9 @@ | |||
186 | <div i18n class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div> | 174 | <div i18n class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div> |
187 | 175 | ||
188 | <my-markdown-textarea | 176 | <my-markdown-textarea |
189 | name="instanceCreationReason" formControlName="creationReason" textareaMaxWidth="500px" | 177 | name="instanceCreationReason" formControlName="creationReason" |
190 | [ngClass]="{ 'input-error': formErrors['instance.creationReason'] }" | 178 | [formError]="formErrors['instance.creationReason']" |
191 | ></my-markdown-textarea> | 179 | ></my-markdown-textarea> |
192 | |||
193 | <div *ngIf="formErrors.instance.creationReason" class="form-error">{{ formErrors.instance.creationReason }}</div> | ||
194 | </div> | 180 | </div> |
195 | 181 | ||
196 | <div class="form-group"> | 182 | <div class="form-group"> |
@@ -198,11 +184,9 @@ | |||
198 | <div i18n class="label-small-info">It's important to know for users who want to register on your instance</div> | 184 | <div i18n class="label-small-info">It's important to know for users who want to register on your instance</div> |
199 | 185 | ||
200 | <my-markdown-textarea | 186 | <my-markdown-textarea |
201 | name="instanceMaintenanceLifetime" formControlName="maintenanceLifetime" textareaMaxWidth="500px" | 187 | name="instanceMaintenanceLifetime" formControlName="maintenanceLifetime" |
202 | [ngClass]="{ 'input-error': formErrors['instance.maintenanceLifetime'] }" | 188 | [formError]="formErrors['instance.maintenanceLifetime']" |
203 | ></my-markdown-textarea> | 189 | ></my-markdown-textarea> |
204 | |||
205 | <div *ngIf="formErrors.instance.maintenanceLifetime" class="form-error">{{ formErrors.instance.maintenanceLifetime }}</div> | ||
206 | </div> | 190 | </div> |
207 | 191 | ||
208 | <div class="form-group"> | 192 | <div class="form-group"> |
@@ -210,33 +194,29 @@ | |||
210 | <div i18n class="label-small-info">With your own funds? With user donations? Advertising?</div> | 194 | <div i18n class="label-small-info">With your own funds? With user donations? Advertising?</div> |
211 | 195 | ||
212 | <my-markdown-textarea | 196 | <my-markdown-textarea |
213 | name="instanceBusinessModel" formControlName="businessModel" textareaMaxWidth="500px" | 197 | name="instanceBusinessModel" formControlName="businessModel" |
214 | [ngClass]="{ 'input-error': formErrors['instance.businessModel'] }" | 198 | [formError]="formErrors['instance.businessModel']" |
215 | ></my-markdown-textarea> | 199 | ></my-markdown-textarea> |
216 | |||
217 | <div *ngIf="formErrors.instance.businessModel" class="form-error">{{ formErrors.instance.businessModel }}</div> | ||
218 | </div> | 200 | </div> |
219 | 201 | ||
220 | </div> | 202 | </div> |
221 | </div> | 203 | </div> |
222 | 204 | ||
223 | <div class="form-row mt-4"> <!-- other information grid --> | 205 | <div class="row mt-4"> <!-- other information grid --> |
224 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 206 | <div class="col-12 col-lg-4 col-xl-3"> |
225 | <div i18n class="inner-form-title">OTHER INFORMATION</div> | 207 | <div i18n class="inner-form-title">OTHER INFORMATION</div> |
226 | </div> | 208 | </div> |
227 | 209 | ||
228 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 210 | <div class="col-12 col-lg-8 col-xl-9"> |
229 | 211 | ||
230 | <div class="form-group"> | 212 | <div class="form-group"> |
231 | <label i18n for="instanceHardwareInformation">What server/hardware does the instance run on?</label> | 213 | <label i18n for="instanceHardwareInformation">What server/hardware does the instance run on?</label> |
232 | <div i18n class="label-small-info">i.e. 2vCore 2GB RAM, a direct the link to the server you rent, etc.</div> | 214 | <div i18n class="label-small-info">i.e. 2vCore 2GB RAM, a direct the link to the server you rent, etc.</div> |
233 | 215 | ||
234 | <my-markdown-textarea | 216 | <my-markdown-textarea |
235 | name="instanceHardwareInformation" formControlName="hardwareInformation" textareaMaxWidth="500px" | 217 | name="instanceHardwareInformation" formControlName="hardwareInformation" |
236 | [classes]="{ 'input-error': formErrors['instance.hardwareInformation'] }" | 218 | [formError]="formErrors['instance.hardwareInformation']" |
237 | ></my-markdown-textarea> | 219 | ></my-markdown-textarea> |
238 | |||
239 | <div *ngIf="formErrors.instance.hardwareInformation" class="form-error">{{ formErrors.instance.hardwareInformation }}</div> | ||
240 | </div> | 220 | </div> |
241 | 221 | ||
242 | </div> | 222 | </div> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html index 71d5d91f0..ae79e54fc 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html | |||
@@ -1,14 +1,14 @@ | |||
1 | <ng-container [formGroup]="form"> | 1 | <ng-container [formGroup]="form"> |
2 | 2 | ||
3 | <div class="form-row mt-5"> | 3 | <div class="row mt-5"> |
4 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 4 | <div class="col-12 col-lg-4 col-xl-3"> |
5 | <div i18n class="inner-form-title">LIVE</div> | 5 | <div i18n class="inner-form-title">LIVE</div> |
6 | <div i18n class="inner-form-description"> | 6 | <div i18n class="inner-form-description"> |
7 | Enable users of your instance to stream live. | 7 | Enable users of your instance to stream live. |
8 | </div> | 8 | </div> |
9 | </div> | 9 | </div> |
10 | 10 | ||
11 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 11 | <div class="col-12 col-lg-8 col-xl-9"> |
12 | 12 | ||
13 | <ng-container formGroupName="live"> | 13 | <ng-container formGroupName="live"> |
14 | 14 | ||
@@ -46,9 +46,9 @@ | |||
46 | </div> | 46 | </div> |
47 | 47 | ||
48 | <div class="form-group" [ngClass]="getDisabledLiveClass()"> | 48 | <div class="form-group" [ngClass]="getDisabledLiveClass()"> |
49 | <label i18n for="liveMaxInstanceLives"> | 49 | <label i18n for="liveMaxInstanceLives">Max simultaneous lives created on your instance</label> |
50 | Max simultaneous lives created on your instance <span class="muted">(-1 for "unlimited")</span> | 50 | |
51 | </label> | 51 | <span class="ms-2 small muted">(-1 for "unlimited")</span> |
52 | 52 | ||
53 | <div class="number-with-unit"> | 53 | <div class="number-with-unit"> |
54 | <input type="number" name="liveMaxInstanceLives" formControlName="maxInstanceLives" /> | 54 | <input type="number" name="liveMaxInstanceLives" formControlName="maxInstanceLives" /> |
@@ -59,9 +59,8 @@ | |||
59 | </div> | 59 | </div> |
60 | 60 | ||
61 | <div class="form-group" [ngClass]="getDisabledLiveClass()"> | 61 | <div class="form-group" [ngClass]="getDisabledLiveClass()"> |
62 | <label i18n for="liveMaxUserLives"> | 62 | <label i18n for="liveMaxUserLives">Max simultaneous lives created per user</label> |
63 | Max simultaneous lives created per user <span class="muted">(-1 for "unlimited")</span> | 63 | <span class="ms-2 small muted">(-1 for "unlimited")</span> |
64 | </label> | ||
65 | 64 | ||
66 | <div class="number-with-unit"> | 65 | <div class="number-with-unit"> |
67 | <input type="number" name="liveMaxUserLives" formControlName="maxUserLives" /> | 66 | <input type="number" name="liveMaxUserLives" formControlName="maxUserLives" /> |
@@ -89,15 +88,15 @@ | |||
89 | </div> | 88 | </div> |
90 | </div> | 89 | </div> |
91 | 90 | ||
92 | <div class="form-row"> <!-- transcoding live streams grid --> | 91 | <div class="row"> <!-- transcoding live streams grid --> |
93 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 92 | <div class="col-12 col-lg-4 col-xl-3"> |
94 | <div i18n class="inner-form-title">TRANSCODING</div> | 93 | <div i18n class="inner-form-title">TRANSCODING</div> |
95 | <div i18n class="inner-form-description"> | 94 | <div i18n class="inner-form-description"> |
96 | Same as VOD transcoding, transcoding live streams so that they are in a streamable form that any device can play. Requires a beefy CPU, and then some. | 95 | Same as VOD transcoding, transcoding live streams so that they are in a streamable form that any device can play. Requires a beefy CPU, and then some. |
97 | </div> | 96 | </div> |
98 | </div> | 97 | </div> |
99 | 98 | ||
100 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 99 | <div class="col-12 col-lg-8 col-xl-9"> |
101 | 100 | ||
102 | <ng-container formGroupName="live"> | 101 | <ng-container formGroupName="live"> |
103 | <ng-container formGroupName="transcoding"> | 102 | <ng-container formGroupName="transcoding"> |
@@ -115,7 +114,7 @@ | |||
115 | <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()"> | 114 | <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()"> |
116 | <label i18n for="liveTranscodingThreads">Live resolutions to generate</label> | 115 | <label i18n for="liveTranscodingThreads">Live resolutions to generate</label> |
117 | 116 | ||
118 | <div class="ml-2 mt-2 d-flex flex-column"> | 117 | <div class="ms-2 mt-2 d-flex flex-column"> |
119 | <ng-container formGroupName="resolutions"> | 118 | <ng-container formGroupName="resolutions"> |
120 | 119 | ||
121 | <div class="form-group" *ngFor="let resolution of liveResolutions"> | 120 | <div class="form-group" *ngFor="let resolution of liveResolutions"> |
@@ -136,7 +135,7 @@ | |||
136 | <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()"> | 135 | <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()"> |
137 | <label i18n for="liveTranscodingThreads">Live transcoding threads</label> | 136 | <label i18n for="liveTranscodingThreads">Live transcoding threads</label> |
138 | 137 | ||
139 | <span class="muted ml-1"> | 138 | <span class="small muted ms-1"> |
140 | <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n> | 139 | <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n> |
141 | will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with VOD transcoding | 140 | will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with VOD transcoding |
142 | </ng-container> | 141 | </ng-container> |
@@ -157,7 +156,7 @@ | |||
157 | 156 | ||
158 | <div class="form-group mt-4" [ngClass]="getDisabledLiveTranscodingClass()"> | 157 | <div class="form-group mt-4" [ngClass]="getDisabledLiveTranscodingClass()"> |
159 | <label i18n for="liveTranscodingProfile">Live transcoding profile</label> | 158 | <label i18n for="liveTranscodingProfile">Live transcoding profile</label> |
160 | <span class="muted ml-1" i18n>new live transcoding profiles can be added by PeerTube plugins</span> | 159 | <span class="small muted ms-1" i18n>new live transcoding profiles can be added by PeerTube plugins</span> |
161 | 160 | ||
162 | <my-select-options | 161 | <my-select-options |
163 | id="liveTranscodingProfile" | 162 | id="liveTranscodingProfile" |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html index 5c0bea4a5..66e421b16 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html | |||
@@ -1,28 +1,23 @@ | |||
1 | <ng-container [formGroup]="form"> | 1 | <ng-container [formGroup]="form"> |
2 | 2 | ||
3 | <div class="form-row mt-4"> <!-- transcoding grid --> | 3 | <div class="row mt-4"> <!-- transcoding grid --> |
4 | <div class="form-group col-12 col-lg-4 col-xl-3"></div> | 4 | <div class="col-12 col-lg-4 col-xl-3"></div> |
5 | <div class="form-group form-group-right col-12 col-lg-8"> | 5 | <div class="col-12 col-lg-8"> |
6 | 6 | ||
7 | <div class="callout callout-info"> | 7 | <div class="callout callout-orange"> |
8 | <span i18n> | 8 | <span i18n> |
9 | Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically. | 9 | Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically. |
10 | </span> | 10 | </span> |
11 | |||
11 | <span i18n> | 12 | <span i18n> |
12 | However, you may want to read our guidelines before tweaking the following values. | 13 | However, you may want to read <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/admin-configuration?id=transcoding">our guidelines</a> before tweaking the following values. |
13 | </span> | 14 | </span> |
14 | |||
15 | <div class="callout-container"> | ||
16 | <a class="callout-link" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/admin-configuration?id=transcoding" i18n> | ||
17 | Read guidelines | ||
18 | </a> | ||
19 | </div> | ||
20 | </div> | 15 | </div> |
21 | </div> | 16 | </div> |
22 | </div> | 17 | </div> |
23 | 18 | ||
24 | <div class="form-row mt-2"> <!-- transcoding grid --> | 19 | <div class="row mt-4"> <!-- transcoding grid --> |
25 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 20 | <div class="col-12 col-lg-4 col-xl-3"> |
26 | <div i18n class="inner-form-title">TRANSCODING</div> | 21 | <div i18n class="inner-form-title">TRANSCODING</div> |
27 | <div i18n class="inner-form-description"> | 22 | <div i18n class="inner-form-description"> |
28 | Process uploaded videos so that they are in a streamable form that any device can play. Though costly in | 23 | Process uploaded videos so that they are in a streamable form that any device can play. Though costly in |
@@ -30,11 +25,11 @@ | |||
30 | </div> | 25 | </div> |
31 | </div> | 26 | </div> |
32 | 27 | ||
33 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 28 | <div class="col-12 col-lg-8 col-xl-9"> |
34 | 29 | ||
35 | <ng-container formGroupName="transcoding"> | 30 | <ng-container formGroupName="transcoding"> |
36 | 31 | ||
37 | <div class="form-group mb-0 col-12 col-xl-11"> | 32 | <div class="col-12 col-xl-11"> |
38 | <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled" [recommended]="true"> | 33 | <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled" [recommended]="true"> |
39 | <ng-template ptTemplate="label"> | 34 | <ng-template ptTemplate="label"> |
40 | <ng-container i18n>Transcoding enabled</ng-container> | 35 | <ng-container i18n>Transcoding enabled</ng-container> |
@@ -115,7 +110,11 @@ | |||
115 | <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> | 110 | <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> |
116 | <label i18n>Resolutions to generate per enabled format</label> | 111 | <label i18n>Resolutions to generate per enabled format</label> |
117 | 112 | ||
118 | <div class="ml-2 mt-2 d-flex flex-column"> | 113 | <div class="ms-2 d-flex flex-column"> |
114 | <span class="mb-3 small muted" i18n> | ||
115 | The original file resolution will be the default target if no option is selected. | ||
116 | </span> | ||
117 | |||
119 | <ng-container formGroupName="resolutions"> | 118 | <ng-container formGroupName="resolutions"> |
120 | <div class="form-group" *ngFor="let resolution of resolutions"> | 119 | <div class="form-group" *ngFor="let resolution of resolutions"> |
121 | <my-peertube-checkbox | 120 | <my-peertube-checkbox |
@@ -127,10 +126,6 @@ | |||
127 | </ng-template> | 126 | </ng-template> |
128 | </my-peertube-checkbox> | 127 | </my-peertube-checkbox> |
129 | </div> | 128 | </div> |
130 | |||
131 | <span class="mb-2 muted" i18n> | ||
132 | The original file resolution will be the default target if no option is selected. | ||
133 | </span> | ||
134 | </ng-container> | 129 | </ng-container> |
135 | </div> | 130 | </div> |
136 | </div> | 131 | </div> |
@@ -142,7 +137,8 @@ | |||
142 | 137 | ||
143 | <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> | 138 | <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> |
144 | <label i18n for="transcodingThreads">Transcoding threads</label> | 139 | <label i18n for="transcodingThreads">Transcoding threads</label> |
145 | <span class="muted ml-1"> | 140 | |
141 | <span class="small muted ms-1"> | ||
146 | <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n> | 142 | <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n> |
147 | will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with live transcoding | 143 | will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with live transcoding |
148 | </ng-container> | 144 | </ng-container> |
@@ -162,9 +158,9 @@ | |||
162 | <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div> | 158 | <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div> |
163 | </div> | 159 | </div> |
164 | 160 | ||
165 | <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> | 161 | <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> |
166 | <label i18n for="transcodingConcurrency">Transcoding jobs concurrency</label> | 162 | <label i18n for="transcodingConcurrency">Transcoding jobs concurrency</label> |
167 | <span class="muted ml-1" i18n>allows to transcode multiple files in parallel. ⚠️ Requires a PeerTube restart</span> | 163 | <span class="small muted ms-1" i18n>allows to transcode multiple files in parallel. ⚠️ Requires a PeerTube restart</span> |
168 | 164 | ||
169 | <div class="number-with-unit"> | 165 | <div class="number-with-unit"> |
170 | <input type="number" name="transcodingConcurrency" formControlName="concurrency" /> | 166 | <input type="number" name="transcodingConcurrency" formControlName="concurrency" /> |
@@ -174,9 +170,9 @@ | |||
174 | <div *ngIf="formErrors.transcoding.concurrency" class="form-error">{{ formErrors.transcoding.concurrency }}</div> | 170 | <div *ngIf="formErrors.transcoding.concurrency" class="form-error">{{ formErrors.transcoding.concurrency }}</div> |
175 | </div> | 171 | </div> |
176 | 172 | ||
177 | <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> | 173 | <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> |
178 | <label i18n for="transcodingProfile">Transcoding profile</label> | 174 | <label i18n for="transcodingProfile">Transcoding profile</label> |
179 | <span class="muted ml-1" i18n>new transcoding profiles can be added by PeerTube plugins</span> | 175 | <span class="small muted ms-1" i18n>new transcoding profiles can be added by PeerTube plugins</span> |
180 | 176 | ||
181 | <my-select-options | 177 | <my-select-options |
182 | id="transcodingProfile" | 178 | id="transcodingProfile" |
@@ -193,15 +189,15 @@ | |||
193 | </div> | 189 | </div> |
194 | </div> | 190 | </div> |
195 | 191 | ||
196 | <div class="form-row mt-2"> <!-- video studio grid --> | 192 | <div class="row mt-2"> <!-- video studio grid --> |
197 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 193 | <div class="col-12 col-lg-4 col-xl-3"> |
198 | <div i18n class="inner-form-title">VIDEO STUDIO</div> | 194 | <div i18n class="inner-form-title">VIDEO STUDIO</div> |
199 | <div i18n class="inner-form-description"> | 195 | <div i18n class="inner-form-description"> |
200 | Allows your users to edit their video (cut, add intro/outro, add a watermark etc) | 196 | Allows your users to edit their video (cut, add intro/outro, add a watermark etc) |
201 | </div> | 197 | </div> |
202 | </div> | 198 | </div> |
203 | 199 | ||
204 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | 200 | <div class="col-12 col-lg-8 col-xl-9"> |
205 | 201 | ||
206 | <ng-container formGroupName="videoStudio"> | 202 | <ng-container formGroupName="videoStudio"> |
207 | <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> | 203 | <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> |
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html index 1df7bb164..5367bf517 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.html +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html | |||
@@ -12,7 +12,7 @@ | |||
12 | > | 12 | > |
13 | <ng-template pTemplate="caption"> | 13 | <ng-template pTemplate="caption"> |
14 | <div class="caption"> | 14 | <div class="caption"> |
15 | <div class="ml-auto"> | 15 | <div class="ms-auto"> |
16 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> | 16 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> |
17 | </div> | 17 | </div> |
18 | </div> | 18 | </div> |
@@ -41,15 +41,15 @@ | |||
41 | <td> | 41 | <td> |
42 | <a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer"> | 42 | <a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer"> |
43 | {{ follow.follower.name + '@' + follow.follower.host }} | 43 | {{ follow.follower.name + '@' + follow.follower.host }} |
44 | <span class="glyphicon glyphicon-new-window"></span> | 44 | <my-global-icon iconName="external-link"></my-global-icon> |
45 | </a> | 45 | </a> |
46 | </td> | 46 | </td> |
47 | 47 | ||
48 | <td *ngIf="follow.state === 'accepted'"> | 48 | <td *ngIf="follow.state === 'accepted'"> |
49 | <span class="badge badge-green" i18n>Accepted</span> | 49 | <span class="pt-badge badge-green" i18n>Accepted</span> |
50 | </td> | 50 | </td> |
51 | <td *ngIf="follow.state === 'pending'"> | 51 | <td *ngIf="follow.state === 'pending'"> |
52 | <span class="badge badge-yellow" i18n>Pending</span> | 52 | <span class="pt-badge badge-yellow" i18n>Pending</span> |
53 | </td> | 53 | </td> |
54 | 54 | ||
55 | <td>{{ follow.score }}</td> | 55 | <td>{{ follow.score }}</td> |
@@ -59,7 +59,7 @@ | |||
59 | 59 | ||
60 | <ng-template pTemplate="emptymessage"> | 60 | <ng-template pTemplate="emptymessage"> |
61 | <tr> | 61 | <tr> |
62 | <td colspan="6"> | 62 | <td colspan="5"> |
63 | <div class="no-results"> | 63 | <div class="no-results"> |
64 | <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container> | 64 | <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container> |
65 | <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container> | 65 | <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container> |
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts index c40b36e10..07cc75d77 100644 --- a/client/src/app/+admin/follows/following-list/follow-modal.component.ts +++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { prepareIcu } from '@app/helpers' | ||
3 | import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' | 4 | import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
5 | import { InstanceFollowService } from '@app/shared/shared-instance' | 6 | import { InstanceFollowService } from '@app/shared/shared-instance' |
@@ -60,7 +61,13 @@ export class FollowModalComponent extends FormReactive implements OnInit { | |||
60 | this.followService.follow(hostsOrHandles) | 61 | this.followService.follow(hostsOrHandles) |
61 | .subscribe({ | 62 | .subscribe({ |
62 | next: () => { | 63 | next: () => { |
63 | this.notifier.success($localize`Follow request(s) sent!`) | 64 | this.notifier.success( |
65 | prepareIcu($localize`{count, plural, =1 {Follow request sent!} other {Follow requests sent!}}`)( | ||
66 | { count: hostsOrHandles.length }, | ||
67 | $localize`Follow request(s) sent!` | ||
68 | ) | ||
69 | ) | ||
70 | |||
64 | this.newFollow.emit() | 71 | this.newFollow.emit() |
65 | }, | 72 | }, |
66 | 73 | ||
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html index 767e92d18..106e1805e 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.html +++ b/client/src/app/+admin/follows/following-list/following-list.component.html | |||
@@ -19,7 +19,7 @@ | |||
19 | </a> | 19 | </a> |
20 | </div> | 20 | </div> |
21 | 21 | ||
22 | <div class="ml-auto"> | 22 | <div class="ms-auto"> |
23 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> | 23 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> |
24 | </div> | 24 | </div> |
25 | </div> | 25 | </div> |
@@ -43,15 +43,15 @@ | |||
43 | <td> | 43 | <td> |
44 | <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> | 44 | <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> |
45 | {{ follow.following.name + '@' + follow.following.host }} | 45 | {{ follow.following.name + '@' + follow.following.host }} |
46 | <span class="glyphicon glyphicon-new-window"></span> | 46 | <my-global-icon iconName="external-link"></my-global-icon> |
47 | </a> | 47 | </a> |
48 | </td> | 48 | </td> |
49 | 49 | ||
50 | <td *ngIf="follow.state === 'accepted'"> | 50 | <td *ngIf="follow.state === 'accepted'"> |
51 | <span class="badge badge-green" i18n>Accepted</span> | 51 | <span class="pt-badge badge-green" i18n>Accepted</span> |
52 | </td> | 52 | </td> |
53 | <td *ngIf="follow.state === 'pending'"> | 53 | <td *ngIf="follow.state === 'pending'"> |
54 | <span class="badge badge-yellow" i18n>Pending</span> | 54 | <span class="pt-badge badge-yellow" i18n>Pending</span> |
55 | </td> | 55 | </td> |
56 | 56 | ||
57 | <td>{{ follow.createdAt | date: 'short' }}</td> | 57 | <td>{{ follow.createdAt | date: 'short' }}</td> |
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html index f13a0c378..12b07da11 100644 --- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html | |||
@@ -46,7 +46,7 @@ | |||
46 | <td> | 46 | <td> |
47 | <a [href]="redundancy.url" i18n-title title="Open video in a new tab" target="_blank" rel="noopener noreferrer"> | 47 | <a [href]="redundancy.url" i18n-title title="Open video in a new tab" target="_blank" rel="noopener noreferrer"> |
48 | {{ redundancy.name }} | 48 | {{ redundancy.name }} |
49 | <span class="glyphicon glyphicon-new-window"></span> | 49 | <my-global-icon iconName="external-link"></my-global-icon> |
50 | </a> | 50 | </a> |
51 | </td> | 51 | </td> |
52 | 52 | ||
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html index 3634951c9..b302014b6 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html | |||
@@ -13,7 +13,7 @@ | |||
13 | > | 13 | > |
14 | <ng-template pTemplate="caption"> | 14 | <ng-template pTemplate="caption"> |
15 | <div class="caption"> | 15 | <div class="caption"> |
16 | <div class="ml-auto"> | 16 | <div class="ms-auto"> |
17 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> | 17 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> |
18 | </div> | 18 | </div> |
19 | </div> | 19 | </div> |
@@ -53,11 +53,11 @@ | |||
53 | </td> | 53 | </td> |
54 | 54 | ||
55 | <td> | 55 | <td> |
56 | <span *ngIf="videoBlock.video.nsfw" class="badge badge-red" i18n>NSFW</span> | 56 | <span *ngIf="videoBlock.video.nsfw" class="pt-badge badge-red" i18n>NSFW</span> |
57 | </td> | 57 | </td> |
58 | 58 | ||
59 | <td> | 59 | <td> |
60 | <span *ngIf="videoBlock.unfederated" class="badge badge-blue" i18n>Unfederated</span> | 60 | <span *ngIf="videoBlock.unfederated" class="pt-badge badge-blue" i18n>Unfederated</span> |
61 | </td> | 61 | </td> |
62 | 62 | ||
63 | <td> | 63 | <td> |
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.html b/client/src/app/+admin/overview/comments/video-comment-list.component.html index 27a5d82ff..b7fc0a1eb 100644 --- a/client/src/app/+admin/overview/comments/video-comment-list.component.html +++ b/client/src/app/+admin/overview/comments/video-comment-list.component.html | |||
@@ -25,7 +25,7 @@ | |||
25 | </my-action-dropdown> | 25 | </my-action-dropdown> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <div class="ml-auto right-form"> | 28 | <div class="ms-auto right-form"> |
29 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> | 29 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> |
30 | 30 | ||
31 | <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button> | 31 | <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button> |
@@ -68,7 +68,7 @@ | |||
68 | <td> | 68 | <td> |
69 | <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> | 69 | <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> |
70 | <div class="chip two-lines"> | 70 | <div class="chip two-lines"> |
71 | <my-actor-avatar [account]="videoComment.account" size="32"></my-actor-avatar> | 71 | <my-actor-avatar [actor]="videoComment.account" actorType="account" size="32"></my-actor-avatar> |
72 | <div> | 72 | <div> |
73 | {{ videoComment.account.displayName }} | 73 | {{ videoComment.account.displayName }} |
74 | <span>{{ videoComment.by }}</span> | 74 | <span>{{ videoComment.by }}</span> |
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.ts b/client/src/app/+admin/overview/comments/video-comment-list.component.ts index f3f43a900..f01a1629b 100644 --- a/client/src/app/+admin/overview/comments/video-comment-list.component.ts +++ b/client/src/app/+admin/overview/comments/video-comment-list.component.ts | |||
@@ -7,6 +7,7 @@ import { DropdownAction } from '@app/shared/shared-main' | |||
7 | import { BulkService } from '@app/shared/shared-moderation' | 7 | import { BulkService } from '@app/shared/shared-moderation' |
8 | import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' | 8 | import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' |
9 | import { FeedFormat, UserRight } from '@shared/models' | 9 | import { FeedFormat, UserRight } from '@shared/models' |
10 | import { prepareIcu } from '@app/helpers' | ||
10 | 11 | ||
11 | @Component({ | 12 | @Component({ |
12 | selector: 'my-video-comment-list', | 13 | selector: 'my-video-comment-list', |
@@ -145,7 +146,13 @@ export class VideoCommentListComponent extends RestTable implements OnInit { | |||
145 | this.videoCommentService.deleteVideoComments(commentArgs) | 146 | this.videoCommentService.deleteVideoComments(commentArgs) |
146 | .subscribe({ | 147 | .subscribe({ |
147 | next: () => { | 148 | next: () => { |
148 | this.notifier.success($localize`${commentArgs.length} comments deleted.`) | 149 | this.notifier.success( |
150 | prepareIcu($localize`{count, plural, =1 {1 comment deleted.} other {{count} comments deleted.}}`)( | ||
151 | { count: commentArgs.length }, | ||
152 | $localize`${commentArgs.length} comment(s) deleted.` | ||
153 | ) | ||
154 | ) | ||
155 | |||
149 | this.reloadData() | 156 | this.reloadData() |
150 | }, | 157 | }, |
151 | 158 | ||
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html index 772ebf272..e484ab8b0 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html +++ b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html | |||
@@ -57,7 +57,7 @@ | |||
57 | </div> | 57 | </div> |
58 | </ng-template> | 58 | </ng-template> |
59 | 59 | ||
60 | <div class="form-row" *ngIf="!isInBigView()"> <!-- hidden on large screens, as it is then displayed on the right side of the form --> | 60 | <div class="row d-xxl-none"> <!-- hidden on large screens, as it is then displayed on the right side of the form --> |
61 | <div class="col-12 col-xl-3"></div> | 61 | <div class="col-12 col-xl-3"></div> |
62 | 62 | ||
63 | <div class="col-12 col-xl-9"> | 63 | <div class="col-12 col-xl-9"> |
@@ -67,8 +67,8 @@ | |||
67 | 67 | ||
68 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 68 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
69 | 69 | ||
70 | <div class="form-row mt-4"> <!-- user grid --> | 70 | <div class="row mt-4"> <!-- user grid --> |
71 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 71 | <div class="col-12 col-lg-4 col-xl-3"> |
72 | <div class="anchor" id="user"></div> <!-- user anchor --> | 72 | <div class="anchor" id="user"></div> <!-- user anchor --> |
73 | <div *ngIf="isCreation()" class="account-title" i18n>NEW USER</div> | 73 | <div *ngIf="isCreation()" class="account-title" i18n>NEW USER</div> |
74 | <div *ngIf="!isCreation() && user" class="account-title"> | 74 | <div *ngIf="!isCreation() && user" class="account-title"> |
@@ -76,150 +76,144 @@ | |||
76 | </div> | 76 | </div> |
77 | </div> | 77 | </div> |
78 | 78 | ||
79 | <div class="form-group col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }"> | 79 | <div class="col-12 col-lg-8 col-xl-9"> |
80 | 80 | <div class="row"> | |
81 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form" [ngClass]="{ 'col-5': isInBigView() }"> | 81 | <form class="col" role="form" (ngSubmit)="formValidated()" [formGroup]="form"> |
82 | <div class="form-group" *ngIf="isCreation()"> | 82 | <div class="form-group" *ngIf="isCreation()"> |
83 | <label i18n for="username">Username</label> | 83 | <label i18n for="username">Username</label> |
84 | <input | 84 | <input |
85 | type="text" id="username" i18n-placeholder placeholder="john" class="form-control" | 85 | type="text" id="username" i18n-placeholder placeholder="john" class="form-control" |
86 | formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }" | 86 | formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }" |
87 | > | 87 | > |
88 | <div *ngIf="formErrors.username" class="form-error"> | 88 | <div *ngIf="formErrors.username" class="form-error"> |
89 | {{ formErrors.username }} | 89 | {{ formErrors.username }} |
90 | </div> | ||
90 | </div> | 91 | </div> |
91 | </div> | ||
92 | 92 | ||
93 | <div class="form-group" *ngIf="isCreation()"> | 93 | <div class="form-group" *ngIf="isCreation()"> |
94 | <label i18n for="channelName">Channel name</label> | 94 | <label i18n for="channelName">Channel name</label> |
95 | <input | 95 | <input |
96 | type="text" id="channelName" i18n-placeholder placeholder="john_channel" class="form-control" | 96 | type="text" id="channelName" i18n-placeholder placeholder="john_channel" class="form-control" |
97 | formControlName="channelName" [ngClass]="{ 'input-error': formErrors['channelName'] }" | 97 | formControlName="channelName" [ngClass]="{ 'input-error': formErrors['channelName'] }" |
98 | > | 98 | > |
99 | <div *ngIf="formErrors.channelName" class="form-error"> | 99 | <div *ngIf="formErrors.channelName" class="form-error"> |
100 | {{ formErrors.channelName }} | 100 | {{ formErrors.channelName }} |
101 | </div> | ||
101 | </div> | 102 | </div> |
102 | </div> | ||
103 | 103 | ||
104 | <div class="form-group"> | 104 | <div class="form-group"> |
105 | <label i18n for="email">Email</label> | 105 | <label i18n for="email">Email</label> |
106 | <input | 106 | <input |
107 | type="text" id="email" i18n-placeholder placeholder="mail@example.com" class="form-control" | 107 | type="text" id="email" i18n-placeholder placeholder="mail@example.com" class="form-control" |
108 | formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }" | 108 | formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }" |
109 | autocomplete="off" [readonly]="user && user.pluginAuth !== null" | 109 | autocomplete="off" [readonly]="user && user.pluginAuth !== null" |
110 | > | 110 | > |
111 | <div *ngIf="formErrors.email" class="form-error"> | 111 | <div *ngIf="formErrors.email" class="form-error"> |
112 | {{ formErrors.email }} | 112 | {{ formErrors.email }} |
113 | </div> | ||
113 | </div> | 114 | </div> |
114 | </div> | ||
115 | 115 | ||
116 | <div class="form-group" *ngIf="isCreation()"> | 116 | <div class="form-group" *ngIf="isCreation()"> |
117 | <label i18n for="password">Password</label> | 117 | <label i18n for="password">Password</label> |
118 | <my-help *ngIf="isPasswordOptional()"> | 118 | <my-help *ngIf="isPasswordOptional()"> |
119 | <ng-template ptTemplate="customHtml"> | 119 | <ng-template ptTemplate="customHtml"> |
120 | <ng-container i18n> | 120 | <ng-container i18n> |
121 | If you leave the password empty, an email will be sent to the user. | 121 | If you leave the password empty, an email will be sent to the user. |
122 | </ng-container> | 122 | </ng-container> |
123 | </ng-template> | 123 | </ng-template> |
124 | </my-help> | 124 | </my-help> |
125 | 125 | ||
126 | <my-input-toggle-hidden | 126 | <my-input-text formControlName="password" inputId="password" [formError]="formErrors['password']" autocomplete="new-password"></my-input-text> |
127 | formControlName="password" inputId="password" [ngClass]="{ 'input-error': formErrors['password'] }" autocomplete="new-password" | ||
128 | ></my-input-toggle-hidden> | ||
129 | |||
130 | <div *ngIf="formErrors.password" class="form-error"> | ||
131 | {{ formErrors.password }} | ||
132 | </div> | 127 | </div> |
133 | </div> | ||
134 | 128 | ||
135 | <div class="form-group"> | 129 | <div class="form-group"> |
136 | <label i18n for="role">Role</label> | 130 | <label i18n for="role">Role</label> |
137 | <div class="peertube-select-container"> | 131 | <div class="peertube-select-container"> |
138 | <select id="role" formControlName="role" class="form-control"> | 132 | <select id="role" formControlName="role" class="form-control"> |
139 | <option *ngFor="let role of roles" [value]="role.value"> | 133 | <option *ngFor="let role of roles" [value]="role.value"> |
140 | {{ role.label }} | 134 | {{ role.label }} |
141 | </option> | 135 | </option> |
142 | </select> | 136 | </select> |
137 | </div> | ||
138 | |||
139 | <div *ngIf="formErrors.role" class="form-error"> | ||
140 | {{ formErrors.role }} | ||
141 | </div> | ||
143 | </div> | 142 | </div> |
144 | 143 | ||
145 | <div *ngIf="formErrors.role" class="form-error"> | 144 | <div class="form-group"> |
146 | {{ formErrors.role }} | 145 | <label i18n for="videoQuota">Video quota</label> |
146 | |||
147 | <my-select-custom-value | ||
148 | id="videoQuota" | ||
149 | [items]="videoQuotaOptions" | ||
150 | formControlName="videoQuota" | ||
151 | i18n-inputSuffix inputSuffix="bytes" inputType="number" | ||
152 | [clearable]="false" | ||
153 | ></my-select-custom-value> | ||
154 | |||
155 | <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()"> | ||
156 | Transcoding is enabled. The video quota only takes into account <strong>original</strong> video size. <br /> | ||
157 | At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}. | ||
158 | </div> | ||
159 | |||
160 | <div *ngIf="formErrors.videoQuota" class="form-error"> | ||
161 | {{ formErrors.videoQuota }} | ||
162 | </div> | ||
147 | </div> | 163 | </div> |
148 | </div> | ||
149 | 164 | ||
150 | <div class="form-group"> | 165 | <div class="form-group"> |
151 | <label i18n for="videoQuota">Video quota</label> | 166 | <label i18n for="videoQuotaDaily">Daily video quota</label> |
152 | |||
153 | <my-select-custom-value | ||
154 | id="videoQuota" | ||
155 | [items]="videoQuotaOptions" | ||
156 | formControlName="videoQuota" | ||
157 | i18n-inputSuffix inputSuffix="bytes" inputType="number" | ||
158 | [clearable]="false" | ||
159 | ></my-select-custom-value> | ||
160 | |||
161 | <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()"> | ||
162 | Transcoding is enabled. The video quota only takes into account <strong>original</strong> video size. <br /> | ||
163 | At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}. | ||
164 | </div> | ||
165 | 167 | ||
166 | <div *ngIf="formErrors.videoQuota" class="form-error"> | 168 | <my-select-custom-value |
167 | {{ formErrors.videoQuota }} | 169 | id="videoQuotaDaily" |
168 | </div> | 170 | [items]="videoQuotaDailyOptions" |
169 | </div> | 171 | formControlName="videoQuotaDaily" |
172 | i18n-inputSuffix inputSuffix="bytes" inputType="number" | ||
173 | [clearable]="false" | ||
174 | ></my-select-custom-value> | ||
170 | 175 | ||
171 | <div class="form-group"> | 176 | <div *ngIf="formErrors.videoQuotaDaily" class="form-error"> |
172 | <label i18n for="videoQuotaDaily">Daily video quota</label> | 177 | {{ formErrors.videoQuotaDaily }} |
173 | 178 | </div> | |
174 | <my-select-custom-value | ||
175 | id="videoQuotaDaily" | ||
176 | [items]="videoQuotaDailyOptions" | ||
177 | formControlName="videoQuotaDaily" | ||
178 | i18n-inputSuffix inputSuffix="bytes" inputType="number" | ||
179 | [clearable]="false" | ||
180 | ></my-select-custom-value> | ||
181 | |||
182 | <div *ngIf="formErrors.videoQuotaDaily" class="form-error"> | ||
183 | {{ formErrors.videoQuotaDaily }} | ||
184 | </div> | 179 | </div> |
185 | </div> | ||
186 | 180 | ||
187 | <div class="form-group" *ngIf="!isCreation() && getAuthPlugins().length !== 0"> | 181 | <div class="form-group" *ngIf="!isCreation() && getAuthPlugins().length !== 0"> |
188 | <label i18n for="pluginAuth">Auth plugin</label> | 182 | <label i18n for="pluginAuth">Auth plugin</label> |
189 | 183 | ||
190 | <div class="peertube-select-container"> | 184 | <div class="peertube-select-container"> |
191 | <select id="pluginAuth" formControlName="pluginAuth" class="form-control"> | 185 | <select id="pluginAuth" formControlName="pluginAuth" class="form-control"> |
192 | <option [value]="null" i18n>None (local authentication)</option> | 186 | <option [value]="null" i18n>None (local authentication)</option> |
193 | <option *ngFor="let authPlugin of getAuthPlugins()" [value]="authPlugin">{{ authPlugin }}</option> | 187 | <option *ngFor="let authPlugin of getAuthPlugins()" [value]="authPlugin">{{ authPlugin }}</option> |
194 | </select> | 188 | </select> |
189 | </div> | ||
195 | </div> | 190 | </div> |
196 | </div> | ||
197 | 191 | ||
198 | <div class="form-group"> | 192 | <div class="form-group"> |
199 | <my-peertube-checkbox | 193 | <my-peertube-checkbox |
200 | inputName="byPassAutoBlock" formControlName="byPassAutoBlock" | 194 | inputName="byPassAutoBlock" formControlName="byPassAutoBlock" |
201 | i18n-labelText labelText="Doesn't need review before a video goes public" | 195 | i18n-labelText labelText="Doesn't need review before a video goes public" |
202 | ></my-peertube-checkbox> | 196 | ></my-peertube-checkbox> |
203 | </div> | 197 | </div> |
204 | 198 | ||
205 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> | 199 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> |
206 | </form> | 200 | </form> |
207 | 201 | ||
208 | <div *ngIf="isInBigView()" class="col-7"> | 202 | <div class="d-none d-xxl-block col-7"> |
209 | <ng-template *ngTemplateOutlet="dashboard"></ng-template> | 203 | <ng-template *ngTemplateOutlet="dashboard"></ng-template> |
204 | </div> | ||
210 | </div> | 205 | </div> |
211 | |||
212 | </div> | 206 | </div> |
213 | </div> | 207 | </div> |
214 | 208 | ||
215 | 209 | ||
216 | <div *ngIf="!isCreation() && user && user.pluginAuth === null" class="form-row mt-4"> <!-- danger zone grid --> | 210 | <div *ngIf="!isCreation() && user && user.pluginAuth === null" class="row mt-4"> <!-- danger zone grid --> |
217 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 211 | <div class="col-12 col-lg-4 col-xl-3"> |
218 | <div class="anchor" id="danger"></div> <!-- danger zone anchor --> | 212 | <div class="anchor" id="danger"></div> <!-- danger zone anchor --> |
219 | <div i18n class="account-title account-title-danger">DANGER ZONE</div> | 213 | <div i18n class="account-title account-title-danger">DANGER ZONE</div> |
220 | </div> | 214 | </div> |
221 | 215 | ||
222 | <div class="form-group col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }"> | 216 | <div class="col-12 col-lg-8 col-xl-9"> |
223 | 217 | ||
224 | <div class="danger-zone"> | 218 | <div class="danger-zone"> |
225 | <div class="form-group reset-password-email"> | 219 | <div class="form-group reset-password-email"> |
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss b/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss index d7932154b..254286ae3 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss +++ b/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss | |||
@@ -4,11 +4,6 @@ | |||
4 | 4 | ||
5 | $form-base-input-width: 340px; | 5 | $form-base-input-width: 340px; |
6 | 6 | ||
7 | label { | ||
8 | font-weight: $font-regular; | ||
9 | font-size: 100%; | ||
10 | } | ||
11 | |||
12 | .account-title { | 7 | .account-title { |
13 | @include settings-big-title; | 8 | @include settings-big-title; |
14 | 9 | ||
@@ -22,7 +17,7 @@ input:not([type=submit]) { | |||
22 | display: block; | 17 | display: block; |
23 | } | 18 | } |
24 | 19 | ||
25 | my-input-toggle-hidden { | 20 | my-input-text { |
26 | @include responsive-width($form-base-input-width); | 21 | @include responsive-width($form-base-input-width); |
27 | 22 | ||
28 | display: block; | 23 | display: block; |
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.ts b/client/src/app/+admin/overview/users/user-edit/user-edit.ts index 069b62a53..395d07423 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-edit.ts +++ b/client/src/app/+admin/overview/users/user-edit/user-edit.ts | |||
@@ -46,10 +46,6 @@ export abstract class UserEdit extends FormReactive implements OnInit { | |||
46 | .concat(this.serverConfig.plugin.registeredExternalAuths.map(p => p.npmName)) | 46 | .concat(this.serverConfig.plugin.registeredExternalAuths.map(p => p.npmName)) |
47 | } | 47 | } |
48 | 48 | ||
49 | isInBigView () { | ||
50 | return this.screenService.getWindowInnerWidth() > 1600 | ||
51 | } | ||
52 | |||
53 | buildRoles () { | 49 | buildRoles () { |
54 | const authUser = this.auth.getUser() | 50 | const authUser = this.auth.getUser() |
55 | 51 | ||
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.html b/client/src/app/+admin/overview/users/user-edit/user-password.component.html index 1238d1839..13f57024b 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-password.component.html +++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.html | |||
@@ -1,20 +1,17 @@ | |||
1 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | 1 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> |
2 | <div class="form-group"> | ||
3 | 2 | ||
4 | <div class="input-group"> | 3 | <div class="input-group"> |
5 | <input id="password" [attr.type]="showPassword ? 'text' : 'password'" class="form-control" | 4 | <input id="password" [attr.type]="showPassword ? 'text' : 'password'" class="form-control" |
6 | formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" | 5 | formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" |
7 | > | 6 | > |
8 | <div class="input-group-append"> | 7 | <button class="btn btn-sm btn-outline-secondary" (click)="togglePasswordVisibility()" type="button"> |
9 | <button class="btn btn-sm btn-outline-secondary" (click)="togglePasswordVisibility()" type="button"> | 8 | <ng-container *ngIf="!showPassword" i18n>Show</ng-container> |
10 | <ng-container *ngIf="!showPassword" i18n>Show</ng-container> | 9 | <ng-container *ngIf="!!showPassword" i18n>Hide</ng-container> |
11 | <ng-container *ngIf="!!showPassword" i18n>Hide</ng-container> | 10 | </button> |
12 | </button> | 11 | </div> |
13 | </div> | 12 | |
14 | </div> | 13 | <div *ngIf="formErrors.password" class="form-error"> |
15 | <div *ngIf="formErrors.password" class="form-error"> | 14 | {{ formErrors.password }} |
16 | {{ formErrors.password }} | ||
17 | </div> | ||
18 | </div> | 15 | </div> |
19 | 16 | ||
20 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> | 17 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> |
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.scss b/client/src/app/+admin/overview/users/user-edit/user-password.component.scss index acb680682..54f782086 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-password.component.scss +++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.scss | |||
@@ -1,13 +1,9 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | input:not([type=submit]):not([type=checkbox]) { | 4 | input[type=text], |
5 | input[type=password] { | ||
5 | @include peertube-input-text(340px); | 6 | @include peertube-input-text(340px); |
6 | |||
7 | display: block; | ||
8 | border-top-right-radius: 0; | ||
9 | border-bottom-right-radius: 0; | ||
10 | border-right: 0; | ||
11 | } | 7 | } |
12 | 8 | ||
13 | input[type=submit] { | 9 | input[type=submit] { |
@@ -17,7 +13,3 @@ input[type=submit] { | |||
17 | 13 | ||
18 | margin-top: 10px; | 14 | margin-top: 10px; |
19 | } | 15 | } |
20 | |||
21 | .input-group-append { | ||
22 | height: 30px; | ||
23 | } | ||
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html index 30d10e3cf..aa3def63a 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.html +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html | |||
@@ -5,7 +5,7 @@ | |||
5 | 5 | ||
6 | <p-table | 6 | <p-table |
7 | [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" | 7 | [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" |
8 | [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers" | 8 | [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers" |
9 | [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" | 9 | [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" |
10 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate | 10 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate |
11 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" | 11 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" |
@@ -26,7 +26,7 @@ | |||
26 | </a> | 26 | </a> |
27 | </div> | 27 | </div> |
28 | 28 | ||
29 | <div class="ml-auto"> | 29 | <div class="ms-auto"> |
30 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> | 30 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> |
31 | </div> | 31 | </div> |
32 | 32 | ||
@@ -90,24 +90,24 @@ | |||
90 | </my-user-moderation-dropdown> | 90 | </my-user-moderation-dropdown> |
91 | </td> | 91 | </td> |
92 | 92 | ||
93 | <td *ngIf="isSelected('username')"> | 93 | <td *ngIf="isSelected('username')" class="cell-username"> |
94 | <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/a/' + user.username ]"> | 94 | <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/a/' + user.username ]"> |
95 | <div class="chip two-lines"> | 95 | <div class="chip two-lines"> |
96 | <my-actor-avatar [account]="user?.account" size="32"></my-actor-avatar> | 96 | <my-actor-avatar [actor]="user?.account" actorType="account" size="32"></my-actor-avatar> |
97 | <div> | 97 | <div> |
98 | <span class="user-table-primary-text">{{ user.account.displayName }}</span> | 98 | <span class="user-table-primary-text">{{ user.account.displayName }}</span> |
99 | <span class="muted">{{ user.username }}</span> | 99 | <span class="muted">{{ user.username }}</span> |
100 | </div> | 100 | </div> |
101 | </div> | 101 | </div> |
102 | </a> | 102 | </a> |
103 | 103 | ||
104 | <div *ngIf="user.accountMutedStatus.mutedByInstance" class="badges-username badge badge-red" i18n>Muted</div> | 104 | <div *ngIf="user.accountMutedStatus.mutedByInstance" class="pt-badge badge-red" i18n>Muted</div> |
105 | <div *ngIf="user.blocked" class="badges-username badge badge-red" i18n>Banned</div> | 105 | <div *ngIf="user.blocked" class="pt-badge badge-red" i18n>Banned</div> |
106 | </td> | 106 | </td> |
107 | 107 | ||
108 | <td *ngIf="isSelected('role')"> | 108 | <td *ngIf="isSelected('role')"> |
109 | <span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span> | 109 | <span *ngIf="user.blocked" class="pt-badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span> |
110 | <span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span> | 110 | <span *ngIf="!user.blocked" class="pt-badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span> |
111 | </td> | 111 | </td> |
112 | 112 | ||
113 | <td *ngIf="isSelected('email')" [title]="user.email"> | 113 | <td *ngIf="isSelected('email')" [title]="user.email"> |
@@ -139,7 +139,7 @@ | |||
139 | 139 | ||
140 | <td *ngIf="isSelected('quotaDaily')"> | 140 | <td *ngIf="isSelected('quotaDaily')"> |
141 | <div class="progress" i18n-title title="Total daily video quota"> | 141 | <div class="progress" i18n-title title="Total daily video quota"> |
142 | <div class="progress-bar secondary" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }" | 142 | <div class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }" |
143 | [attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily"> | 143 | [attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily"> |
144 | </div> | 144 | </div> |
145 | <span>{{ user.videoQuotaUsedDaily }}</span> | 145 | <span>{{ user.videoQuotaUsedDaily }}</span> |
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.scss b/client/src/app/+admin/overview/users/user-list/user-list.component.scss index 8160703f0..3c775cac5 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.scss | |||
@@ -1,5 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | @use '~bootstrap/scss/functions' as *; | ||
3 | 4 | ||
4 | .add-button { | 5 | .add-button { |
5 | @include create-button; | 6 | @include create-button; |
@@ -23,15 +24,8 @@ tr.banned > td { | |||
23 | font-weight: $font-semibold; | 24 | font-weight: $font-semibold; |
24 | } | 25 | } |
25 | 26 | ||
26 | .badges-username { | 27 | .cell-username .pt-badge { |
27 | margin-left: 15px; | 28 | @include margin-left(15px); |
28 | } | ||
29 | |||
30 | .user-table-primary-text .glyphicon { | ||
31 | @include margin-left(0.1rem); | ||
32 | |||
33 | font-size: 80%; | ||
34 | color: #808080; | ||
35 | } | 29 | } |
36 | 30 | ||
37 | p-tableCheckbox { | 31 | p-tableCheckbox { |
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts index d22e1355e..3e1a5f6b8 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' | 4 | import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' |
5 | import { getAPIHost } from '@app/helpers' | 5 | import { prepareIcu, getAPIHost } from '@app/helpers' |
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { Actor, DropdownAction } from '@app/shared/shared-main' | 7 | import { Actor, DropdownAction } from '@app/shared/shared-main' |
8 | import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' | 8 | import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' |
@@ -22,6 +22,8 @@ type UserForList = User & { | |||
22 | styleUrls: [ './user-list.component.scss' ] | 22 | styleUrls: [ './user-list.component.scss' ] |
23 | }) | 23 | }) |
24 | export class UserListComponent extends RestTable implements OnInit { | 24 | export class UserListComponent extends RestTable implements OnInit { |
25 | private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns' | ||
26 | |||
25 | @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent | 27 | @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent |
26 | 28 | ||
27 | users: (User & { accountMutedStatus: AccountMutedStatus })[] = [] | 29 | users: (User & { accountMutedStatus: AccountMutedStatus })[] = [] |
@@ -56,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit { | |||
56 | 58 | ||
57 | requiresEmailVerification = false | 59 | requiresEmailVerification = false |
58 | 60 | ||
59 | private _selectedColumns: string[] | 61 | private _selectedColumns: string[] = [] |
60 | 62 | ||
61 | constructor ( | 63 | constructor ( |
62 | protected route: ActivatedRoute, | 64 | protected route: ActivatedRoute, |
@@ -66,7 +68,8 @@ export class UserListComponent extends RestTable implements OnInit { | |||
66 | private serverService: ServerService, | 68 | private serverService: ServerService, |
67 | private auth: AuthService, | 69 | private auth: AuthService, |
68 | private blocklist: BlocklistService, | 70 | private blocklist: BlocklistService, |
69 | private userAdminService: UserAdminService | 71 | private userAdminService: UserAdminService, |
72 | private peertubeLocalStorage: LocalStorageService | ||
70 | ) { | 73 | ) { |
71 | super() | 74 | super() |
72 | } | 75 | } |
@@ -76,11 +79,13 @@ export class UserListComponent extends RestTable implements OnInit { | |||
76 | } | 79 | } |
77 | 80 | ||
78 | get selectedColumns () { | 81 | get selectedColumns () { |
79 | return this._selectedColumns | 82 | return this._selectedColumns || [] |
80 | } | 83 | } |
81 | 84 | ||
82 | set selectedColumns (val: string[]) { | 85 | set selectedColumns (val: string[]) { |
83 | this._selectedColumns = val | 86 | this._selectedColumns = val |
87 | |||
88 | this.saveSelectedColumns() | ||
84 | } | 89 | } |
85 | 90 | ||
86 | ngOnInit () { | 91 | ngOnInit () { |
@@ -126,14 +131,35 @@ export class UserListComponent extends RestTable implements OnInit { | |||
126 | { id: 'role', label: $localize`Role` }, | 131 | { id: 'role', label: $localize`Role` }, |
127 | { id: 'email', label: $localize`Email` }, | 132 | { id: 'email', label: $localize`Email` }, |
128 | { id: 'quota', label: $localize`Video quota` }, | 133 | { id: 'quota', label: $localize`Video quota` }, |
129 | { id: 'createdAt', label: $localize`Created` } | 134 | { id: 'createdAt', label: $localize`Created` }, |
135 | { id: 'lastLoginDate', label: $localize`Last login` }, | ||
136 | |||
137 | { id: 'quotaDaily', label: $localize`Daily quota` }, | ||
138 | { id: 'pluginAuth', label: $localize`Auth plugin` } | ||
130 | ] | 139 | ] |
131 | 140 | ||
132 | this.selectedColumns = this.columns.map(c => c.id) | 141 | this.loadSelectedColumns() |
142 | } | ||
143 | |||
144 | loadSelectedColumns () { | ||
145 | const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY) | ||
133 | 146 | ||
134 | this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` }) | 147 | if (result) { |
135 | this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` }) | 148 | try { |
136 | this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` }) | 149 | this.selectedColumns = JSON.parse(result) |
150 | return | ||
151 | } catch (err) { | ||
152 | console.error('Cannot load selected columns.', err) | ||
153 | } | ||
154 | } | ||
155 | |||
156 | // Default behaviour | ||
157 | this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ] | ||
158 | return | ||
159 | } | ||
160 | |||
161 | saveSelectedColumns () { | ||
162 | this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns)) | ||
137 | } | 163 | } |
138 | 164 | ||
139 | getIdentifier () { | 165 | getIdentifier () { |
@@ -183,13 +209,25 @@ export class UserListComponent extends RestTable implements OnInit { | |||
183 | } | 209 | } |
184 | 210 | ||
185 | async unbanUsers (users: User[]) { | 211 | async unbanUsers (users: User[]) { |
186 | const res = await this.confirmService.confirm($localize`Do you really want to unban ${users.length} users?`, $localize`Unban`) | 212 | const res = await this.confirmService.confirm( |
213 | prepareIcu($localize`Do you really want to unban {count, plural, =1 {1 user} other {{count} users}}?`)( | ||
214 | { count: users.length }, | ||
215 | $localize`Do you really want to unban ${users.length} users?` | ||
216 | ), | ||
217 | $localize`Unban` | ||
218 | ) | ||
219 | |||
187 | if (res === false) return | 220 | if (res === false) return |
188 | 221 | ||
189 | this.userAdminService.unbanUsers(users) | 222 | this.userAdminService.unbanUsers(users) |
190 | .subscribe({ | 223 | .subscribe({ |
191 | next: () => { | 224 | next: () => { |
192 | this.notifier.success($localize`${users.length} users unbanned.`) | 225 | this.notifier.success( |
226 | prepareIcu($localize`{count, plural, =1 {1 user unbanned.} other {{count} users unbanned.}}`)( | ||
227 | { count: users.length }, | ||
228 | $localize`${users.length} users unbanned.` | ||
229 | ) | ||
230 | ) | ||
193 | this.reloadData() | 231 | this.reloadData() |
194 | }, | 232 | }, |
195 | 233 | ||
@@ -198,21 +236,28 @@ export class UserListComponent extends RestTable implements OnInit { | |||
198 | } | 236 | } |
199 | 237 | ||
200 | async removeUsers (users: User[]) { | 238 | async removeUsers (users: User[]) { |
201 | for (const user of users) { | 239 | if (users.some(u => u.username === 'root')) { |
202 | if (user.username === 'root') { | 240 | this.notifier.error($localize`You cannot delete root.`) |
203 | this.notifier.error($localize`You cannot delete root.`) | 241 | return |
204 | return | ||
205 | } | ||
206 | } | 242 | } |
207 | 243 | ||
208 | const message = $localize`If you remove these users, you will not be able to create others with the same username!` | 244 | const message = $localize`<p>You can't create users or channels with a username that already used by a deleted user/channel.</p>` + |
245 | $localize`It means the following usernames will be permanently deleted and cannot be recovered:` + | ||
246 | '<ul>' + users.map(u => '<li>' + u.username + '</li>').join('') + '</ul>' | ||
247 | |||
209 | const res = await this.confirmService.confirm(message, $localize`Delete`) | 248 | const res = await this.confirmService.confirm(message, $localize`Delete`) |
210 | if (res === false) return | 249 | if (res === false) return |
211 | 250 | ||
212 | this.userAdminService.removeUser(users) | 251 | this.userAdminService.removeUser(users) |
213 | .subscribe({ | 252 | .subscribe({ |
214 | next: () => { | 253 | next: () => { |
215 | this.notifier.success($localize`${users.length} users deleted.`) | 254 | this.notifier.success( |
255 | prepareIcu($localize`{count, plural, =1 {1 user deleted.} other {{count} users deleted.}}`)( | ||
256 | { count: users.length }, | ||
257 | $localize`${users.length} users deleted.` | ||
258 | ) | ||
259 | ) | ||
260 | |||
216 | this.reloadData() | 261 | this.reloadData() |
217 | }, | 262 | }, |
218 | 263 | ||
@@ -224,7 +269,13 @@ export class UserListComponent extends RestTable implements OnInit { | |||
224 | this.userAdminService.updateUsers(users, { emailVerified: true }) | 269 | this.userAdminService.updateUsers(users, { emailVerified: true }) |
225 | .subscribe({ | 270 | .subscribe({ |
226 | next: () => { | 271 | next: () => { |
227 | this.notifier.success($localize`${users.length} users email set as verified.`) | 272 | this.notifier.success( |
273 | prepareIcu($localize`{count, plural, =1 {1 user email set as verified.} other {{count} user emails set as verified.}}`)( | ||
274 | { count: users.length }, | ||
275 | $localize`${users.length} users email set as verified.` | ||
276 | ) | ||
277 | ) | ||
278 | |||
228 | this.reloadData() | 279 | this.reloadData() |
229 | }, | 280 | }, |
230 | 281 | ||
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html index 75d9be5f1..2f36c27b7 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.html +++ b/client/src/app/+admin/overview/videos/video-list.component.html | |||
@@ -21,7 +21,7 @@ | |||
21 | </my-action-dropdown> | 21 | </my-action-dropdown> |
22 | </div> | 22 | </div> |
23 | 23 | ||
24 | <div class="ml-auto right-form"> | 24 | <div class="ms-auto right-form"> |
25 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> | 25 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> |
26 | 26 | ||
27 | <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button> | 27 | <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button> |
@@ -67,25 +67,25 @@ | |||
67 | </td> | 67 | </td> |
68 | 68 | ||
69 | <td> | 69 | <td> |
70 | <span class="badge badge-blue" *ngIf="video.isLocal">Local</span> | 70 | <span class="pt-badge badge-blue" *ngIf="video.isLocal">Local</span> |
71 | <span class="badge badge-purple" *ngIf="!video.isLocal">Remote</span> | 71 | <span class="pt-badge badge-purple" *ngIf="!video.isLocal">Remote</span> |
72 | 72 | ||
73 | <span [ngClass]="getPrivacyBadgeClass(video)" class="badge">{{ video.privacy.label }}</span> | 73 | <span [ngClass]="getPrivacyBadgeClass(video)" class="pt-badge">{{ video.privacy.label }}</span> |
74 | 74 | ||
75 | <span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span> | 75 | <span *ngIf="video.nsfw" class="pt-badge badge-red" i18n>NSFW</span> |
76 | 76 | ||
77 | <span *ngIf="isUnpublished(video)" class="badge badge-yellow" i18n>{{ video.state.label }}</span> | 77 | <span *ngIf="isUnpublished(video)" class="pt-badge badge-yellow" i18n>{{ video.state.label }}</span> |
78 | 78 | ||
79 | <span *ngIf="isAccountBlocked(video)" class="badge badge-red" i18n>Account muted</span> | 79 | <span *ngIf="isAccountBlocked(video)" class="pt-badge badge-red" i18n>Account muted</span> |
80 | <span *ngIf="isServerBlocked(video)" class="badge badge-red" i18n>Server muted</span> | 80 | <span *ngIf="isServerBlocked(video)" class="pt-badge badge-red" i18n>Server muted</span> |
81 | 81 | ||
82 | <span *ngIf="isVideoBlocked(video)" class="badge badge-red" i18n>Blocked</span> | 82 | <span *ngIf="isVideoBlocked(video)" class="pt-badge badge-red" i18n>Blocked</span> |
83 | </td> | 83 | </td> |
84 | 84 | ||
85 | <td> | 85 | <td> |
86 | <span *ngIf="isHLS(video)" class="badge badge-blue">HLS</span> | 86 | <span *ngIf="isHLS(video)" class="pt-badge badge-blue">HLS</span> |
87 | <span *ngIf="isWebTorrent(video)" class="badge badge-blue">WebTorrent ({{ video.files.length }})</span> | 87 | <span *ngIf="isWebTorrent(video)" class="pt-badge badge-blue">WebTorrent ({{ video.files.length }})</span> |
88 | <span *ngIf="video.isLive" class="badge badge-blue">Live</span> | 88 | <span *ngIf="video.isLive" class="pt-badge badge-blue">Live</span> |
89 | 89 | ||
90 | <span *ngIf="!isImport(video) && !video.isLive && video.isLocal">{{ getFilesSize(video) | bytes: 1 }}</span> | 90 | <span *ngIf="!isImport(video) && !video.isLive && video.isLocal">{{ getFilesSize(video) | bytes: 1 }}</span> |
91 | </td> | 91 | </td> |
@@ -121,7 +121,7 @@ | |||
121 | </ul> | 121 | </ul> |
122 | </div> | 122 | </div> |
123 | 123 | ||
124 | <my-embed class="ml-auto" [video]="video"></my-embed> | 124 | <my-embed class="ms-auto" [video]="video"></my-embed> |
125 | </div> | 125 | </div> |
126 | </td> | 126 | </td> |
127 | </tr> | 127 | </tr> |
diff --git a/client/src/app/+admin/overview/videos/video-list.component.scss b/client/src/app/+admin/overview/videos/video-list.component.scss index cb47b6548..dcd41a1b4 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.scss +++ b/client/src/app/+admin/overview/videos/video-list.component.scss | |||
@@ -7,10 +7,8 @@ my-embed { | |||
7 | width: 50%; | 7 | width: 50%; |
8 | } | 8 | } |
9 | 9 | ||
10 | .badge { | 10 | .pt-badge { |
11 | @include peertube-badge; | 11 | @include margin-right(5px); |
12 | |||
13 | margin-right: 5px; | ||
14 | } | 12 | } |
15 | 13 | ||
16 | .video-info > div { | 14 | .video-info > div { |
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts index 82ff372aa..67e52d100 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.ts +++ b/client/src/app/+admin/overview/videos/video-list.component.ts | |||
@@ -3,6 +3,7 @@ import { finalize } from 'rxjs/operators' | |||
3 | import { Component, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' | 5 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' |
6 | import { prepareIcu } from '@app/helpers' | ||
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 7 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 8 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
8 | import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' | 9 | import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' |
@@ -196,14 +197,24 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
196 | } | 197 | } |
197 | 198 | ||
198 | private async removeVideos (videos: Video[]) { | 199 | private async removeVideos (videos: Video[]) { |
199 | const message = $localize`Are you sure you want to delete these ${videos.length} videos?` | 200 | const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)( |
201 | { count: videos.length }, | ||
202 | $localize`Are you sure you want to delete these ${videos.length} videos?` | ||
203 | ) | ||
204 | |||
200 | const res = await this.confirmService.confirm(message, $localize`Delete`) | 205 | const res = await this.confirmService.confirm(message, $localize`Delete`) |
201 | if (res === false) return | 206 | if (res === false) return |
202 | 207 | ||
203 | this.videoService.removeVideo(videos.map(v => v.id)) | 208 | this.videoService.removeVideo(videos.map(v => v.id)) |
204 | .subscribe({ | 209 | .subscribe({ |
205 | next: () => { | 210 | next: () => { |
206 | this.notifier.success($localize`Deleted ${videos.length} videos.`) | 211 | this.notifier.success( |
212 | prepareIcu($localize`Deleted {count, plural, =1 {1 video} other {{count} videos}}.`)( | ||
213 | { count: videos.length }, | ||
214 | $localize`Deleted ${videos.length} videos.` | ||
215 | ) | ||
216 | ) | ||
217 | |||
207 | this.reloadData() | 218 | this.reloadData() |
208 | }, | 219 | }, |
209 | 220 | ||
@@ -215,7 +226,13 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
215 | this.videoBlockService.unblockVideo(videos.map(v => v.id)) | 226 | this.videoBlockService.unblockVideo(videos.map(v => v.id)) |
216 | .subscribe({ | 227 | .subscribe({ |
217 | next: () => { | 228 | next: () => { |
218 | this.notifier.success($localize`Unblocked ${videos.length} videos.`) | 229 | this.notifier.success( |
230 | prepareIcu($localize`Unblocked {count, plural, =1 {1 video} other {{count} videos}}.`)( | ||
231 | { count: videos.length }, | ||
232 | $localize`Unblocked ${videos.length} videos.` | ||
233 | ) | ||
234 | ) | ||
235 | |||
219 | this.reloadData() | 236 | this.reloadData() |
220 | }, | 237 | }, |
221 | 238 | ||
@@ -224,9 +241,21 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
224 | } | 241 | } |
225 | 242 | ||
226 | private async removeVideoFiles (videos: Video[], type: 'hls' | 'webtorrent') { | 243 | private async removeVideoFiles (videos: Video[], type: 'hls' | 'webtorrent') { |
227 | const message = type === 'hls' | 244 | let message: string |
228 | ? $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?` | 245 | |
229 | : $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?` | 246 | if (type === 'hls') { |
247 | // eslint-disable-next-line max-len | ||
248 | message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {{count} HLS streaming playlists}}?`)( | ||
249 | { count: videos.length }, | ||
250 | $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?` | ||
251 | ) | ||
252 | } else { | ||
253 | // eslint-disable-next-line max-len | ||
254 | message = prepareIcu($localize`Are you sure you want to delete WebTorrent files of {count, plural, =1 {1 video} other {{count} videos}}?`)( | ||
255 | { count: videos.length }, | ||
256 | $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?` | ||
257 | ) | ||
258 | } | ||
230 | 259 | ||
231 | const res = await this.confirmService.confirm(message, $localize`Delete`) | 260 | const res = await this.confirmService.confirm(message, $localize`Delete`) |
232 | if (res === false) return | 261 | if (res === false) return |
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html index 33575ef52..c989d2e38 100644 --- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html +++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <my-plugin-navigation [pluginType]="pluginType"></my-plugin-navigation> | 1 | <my-plugin-navigation [pluginType]="pluginType"></my-plugin-navigation> |
2 | 2 | ||
3 | <div class="alert alert-info" i18n *ngIf="pluginInstalled"> | 3 | <div class="alert pt-alert-primary" i18n *ngIf="pluginInstalled"> |
4 | To load your new installed plugins or themes, refresh the page. | 4 | To load your new installed plugins or themes, refresh the page. |
5 | </div> | 5 | </div> |
6 | 6 | ||
@@ -32,9 +32,9 @@ | |||
32 | <ng-container *ngFor="let plugin of plugins" > | 32 | <ng-container *ngFor="let plugin of plugins" > |
33 | <my-plugin-card [plugin]="plugin" [version]="plugin.latestVersion" [pluginType]="pluginType"> | 33 | <my-plugin-card [plugin]="plugin" [version]="plugin.latestVersion" [pluginType]="pluginType"> |
34 | <div ngProjectAs="badges"> | 34 | <div ngProjectAs="badges"> |
35 | <span i18n *ngIf="plugin.installed" class="badge badge-success">Installed</span> | 35 | <span i18n *ngIf="plugin.installed" class="pt-badge badge-success">Installed</span> |
36 | 36 | ||
37 | <span *ngIf="plugin.official" class="badge badge-primary" i18n i18n-title title="This plugin is developed by Framasoft"> | 37 | <span *ngIf="plugin.official" class="pt-badge badge-primary" i18n i18n-title title="This plugin is developed by Framasoft"> |
38 | Official | 38 | Official |
39 | </span> | 39 | </span> |
40 | </div> | 40 | </div> |
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss index 10401e9df..d7b41f4d8 100644 --- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss +++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss | |||
@@ -17,16 +17,13 @@ | |||
17 | 17 | ||
18 | input { | 18 | input { |
19 | @include peertube-input-text(500px); | 19 | @include peertube-input-text(500px); |
20 | |||
21 | height: 35px; | ||
22 | } | 20 | } |
23 | } | 21 | } |
24 | 22 | ||
25 | .badge { | 23 | .pt-badge { |
26 | @include margin-left(15px); | 24 | @include margin-left(15px); |
27 | 25 | ||
28 | font-size: 13px; | 26 | font-size: 13px; |
29 | font-weight: $font-semibold; | ||
30 | } | 27 | } |
31 | 28 | ||
32 | .alert { | 29 | .alert { |
diff --git a/client/src/app/+admin/plugins/shared/plugin-card.component.scss b/client/src/app/+admin/plugins/shared/plugin-card.component.scss index 608064722..7ee3376a7 100644 --- a/client/src/app/+admin/plugins/shared/plugin-card.component.scss +++ b/client/src/app/+admin/plugins/shared/plugin-card.component.scss | |||
@@ -14,12 +14,11 @@ | |||
14 | .plugin-name { | 14 | .plugin-name { |
15 | @include margin-right(10px); | 15 | @include margin-right(10px); |
16 | 16 | ||
17 | font-size: 16px; | ||
18 | font-weight: $font-semibold; | 17 | font-weight: $font-semibold; |
19 | } | 18 | } |
20 | 19 | ||
21 | .plugin-version { | 20 | .plugin-version { |
22 | opacity: 0.6; | 21 | opacity: 0.7; |
23 | } | 22 | } |
24 | 23 | ||
25 | .plugin-icon { | 24 | .plugin-icon { |
diff --git a/client/src/app/+admin/system/debug/debug.component.scss b/client/src/app/+admin/system/debug/debug.component.scss index 7f5e861e2..bcbc3fc7c 100644 --- a/client/src/app/+admin/system/debug/debug.component.scss +++ b/client/src/app/+admin/system/debug/debug.component.scss | |||
@@ -1,11 +1,7 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .root { | 4 | code { |
5 | font-size: 15px; | 5 | font-size: 14px; |
6 | 6 | font-weight: $font-semibold; | |
7 | code { | ||
8 | font-size: 14px; | ||
9 | font-weight: $font-semibold; | ||
10 | } | ||
11 | } | 7 | } |
diff --git a/client/src/app/+admin/system/jobs/jobs.component.html b/client/src/app/+admin/system/jobs/jobs.component.html index 301591786..8068fe626 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.html +++ b/client/src/app/+admin/system/jobs/jobs.component.html | |||
@@ -21,7 +21,7 @@ | |||
21 | <span i18n="Selector for the list displaying jobs, filtering by their state">any</span> | 21 | <span i18n="Selector for the list displaying jobs, filtering by their state">any</span> |
22 | </ng-option> | 22 | </ng-option> |
23 | <ng-option *ngFor="let state of jobStates" [value]="state"> | 23 | <ng-option *ngFor="let state of jobStates" [value]="state"> |
24 | <span class="badge" [ngClass]="getJobStateClass(state)">{{ state }}</span> | 24 | <span class="pt-badge" [ngClass]="getJobStateClass(state)">{{ state }}</span> |
25 | </ng-option> | 25 | </ng-option> |
26 | </ng-select> | 26 | </ng-select> |
27 | </div> | 27 | </div> |
@@ -62,7 +62,7 @@ | |||
62 | <td class="job-priority c-hand" [pRowToggler]="job">{{ job.priority }}</td> | 62 | <td class="job-priority c-hand" [pRowToggler]="job">{{ job.priority }}</td> |
63 | 63 | ||
64 | <td class="job-state c-hand" [pRowToggler]="job" *ngIf="jobState === 'all'"> | 64 | <td class="job-state c-hand" [pRowToggler]="job" *ngIf="jobState === 'all'"> |
65 | <span class="badge" [ngClass]="getJobStateClass(job.state)">{{ job.state }}</span> | 65 | <span class="pt-badge" [ngClass]="getJobStateClass(job.state)">{{ job.state }}</span> |
66 | </td> | 66 | </td> |
67 | 67 | ||
68 | <td *ngIf="hasGlobalProgress()" class="job-progress c-hand" [pRowToggler]="job"> | 68 | <td *ngIf="hasGlobalProgress()" class="job-progress c-hand" [pRowToggler]="job"> |
@@ -107,8 +107,8 @@ | |||
107 | </ng-container> | 107 | </ng-container> |
108 | 108 | ||
109 | <ng-container *ngIf="jobState !== 'all'"> | 109 | <ng-container *ngIf="jobState !== 'all'"> |
110 | <ng-container *ngIf="jobType === 'all'" i18n>No <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container> | 110 | <ng-container *ngIf="jobType === 'all'" i18n>No <span class="pt-badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container> |
111 | <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container> | 111 | <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="pt-badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container> |
112 | </ng-container> | 112 | </ng-container> |
113 | </div> | 113 | </div> |
114 | </div> | 114 | </div> |
diff --git a/client/src/app/+admin/system/jobs/jobs.component.scss b/client/src/app/+admin/system/jobs/jobs.component.scss index a9e5e8d4b..4a76f1783 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.scss +++ b/client/src/app/+admin/system/jobs/jobs.component.scss | |||
@@ -44,10 +44,6 @@ | |||
44 | } | 44 | } |
45 | } | 45 | } |
46 | 46 | ||
47 | td .glyphicon { | ||
48 | @include margin-right(10px); | ||
49 | } | ||
50 | |||
51 | pre { | 47 | pre { |
52 | font-size: 11px; | 48 | font-size: 11px; |
53 | } | 49 | } |
@@ -55,7 +51,3 @@ pre { | |||
55 | .job-error { | 51 | .job-error { |
56 | color: #ff0000; | 52 | color: #ff0000; |
57 | } | 53 | } |
58 | |||
59 | .select-filter-block .badge { | ||
60 | @include peertube-badge; | ||
61 | } | ||
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index dc74354d8..f3a2476f9 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html | |||
@@ -1,15 +1,38 @@ | |||
1 | <h1 i18n class="title-page-v2"> | ||
2 | <strong class="underline-orange">{{ instanceName }}</strong> | ||
3 | > | ||
4 | Login | ||
5 | </h1> | ||
6 | |||
1 | <div class="margin-content"> | 7 | <div class="margin-content"> |
2 | <div i18n class="title-page title-page-single"> | 8 | <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth"> |
3 | Login | ||
4 | </div> | ||
5 | 9 | ||
6 | <div class="alert alert-danger" i18n *ngIf="externalAuthError"> | 10 | <div class="alert pt-alert-primary" role="alert"> |
7 | Sorry but there was an issue with the external login process. Please <a routerLink="/about">contact an administrator</a>. | 11 | <h5 class="alert-heading" i18n> |
8 | </div> | 12 | Logging into an account lets you publish content |
13 | </h5> | ||
9 | 14 | ||
10 | <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth"> | 15 | <p *ngIf="signupAllowed" i18n> |
11 | <div *ngIf="error" class="alert alert-danger">{{ error }} | 16 | This instance allows registration. However, be careful to check the <a class="link-orange terms-anchor" (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a><a class="terms-link" target="_blank" routerLink="/about/instance" fragment="terms">Terms</a> before creating an account. |
12 | <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> | 17 | You may also search for another instance to match your exact needs at: <a class="link-orange" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. |
18 | </p> | ||
19 | |||
20 | <p *ngIf="!signupAllowed" i18n> | ||
21 | Currently this instance doesn't allow for user registration, you may check the <a class="link-orange" (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a> for more details or find an instance that gives you the possibility to sign up for an account and upload your videos there. | ||
22 | Find yours among multiple instances at: <a class="link-orange" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. | ||
23 | </p> | ||
24 | </div> | ||
25 | |||
26 | <div class="alert alert-danger" i18n *ngIf="externalAuthError"> | ||
27 | Sorry but there was an issue with the external login process. Please <a class="link-orange" routerLink="/about">contact an administrator</a>. | ||
28 | </div> | ||
29 | |||
30 | <div *ngIf="error" class="alert alert-danger"> | ||
31 | {{ error }} | ||
32 | |||
33 | <a *ngIf="error === 'User email is not verified.'" class="ms-1 link-orange" i18n routerLink="/verify-account/ask-send-email"> | ||
34 | Request new verification email | ||
35 | </a> | ||
13 | </div> | 36 | </div> |
14 | 37 | ||
15 | <div class="wrapper"> | 38 | <div class="wrapper"> |
@@ -18,16 +41,14 @@ | |||
18 | <form myPluginSelector pluginSelectorId="login-form" role="form" (ngSubmit)="login()" [formGroup]="form"> | 41 | <form myPluginSelector pluginSelectorId="login-form" role="form" (ngSubmit)="login()" [formGroup]="form"> |
19 | <div class="form-group"> | 42 | <div class="form-group"> |
20 | <div> | 43 | <div> |
21 | <label i18n for="username">User</label> | 44 | <label i18n for="username">Username or email address</label> |
22 | <input | 45 | <input |
23 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" | 46 | type="text" id="username" i18n-placeholder placeholder="Example: john@example.com" required tabindex="1" |
24 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" myAutofocus | 47 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" myAutofocus |
25 | > | 48 | > |
26 | </div> | 49 | </div> |
27 | 50 | ||
28 | <div *ngIf="formErrors.username" class="form-error"> | 51 | <div *ngIf="formErrors.username" class="form-error">{{ formErrors.username }}</div> |
29 | {{ formErrors.username }} | ||
30 | </div> | ||
31 | 52 | ||
32 | <div *ngIf="hasUsernameUppercase()" i18n class="form-warning"> | 53 | <div *ngIf="hasUsernameUppercase()" i18n class="form-warning"> |
33 | ⚠️ Most email addresses do not include capital letters. | 54 | ⚠️ Most email addresses do not include capital letters. |
@@ -36,40 +57,22 @@ | |||
36 | 57 | ||
37 | <div class="form-group"> | 58 | <div class="form-group"> |
38 | <label i18n for="password">Password</label> | 59 | <label i18n for="password">Password</label> |
39 | <my-input-toggle-hidden formControlName="password" inputId="password" | 60 | |
40 | i18n-placeholder placeholder="Password" | 61 | <my-input-text |
41 | [ngClass]="{ 'input-error': formErrors['password'] }" | 62 | formControlName="password" inputId="password" i18n-placeholder placeholder="Password" |
42 | autocomplete="current-password" [tabindex]="2"></my-input-toggle-hidden> | 63 | [formError]="formErrors['password']" autocomplete="current-password" [tabindex]="2" |
43 | <div *ngIf="formErrors.password" class="form-error"> | 64 | ></my-input-text> |
44 | {{ formErrors.password }} | ||
45 | </div> | ||
46 | </div> | 65 | </div> |
47 | 66 | ||
48 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> | 67 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> |
49 | 68 | ||
50 | <div class="additionnal-links"> | 69 | <div class="additional-links"> |
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> | 70 | <a i18n role="button" class="link-orange" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> |
52 | 71 | ||
53 | <div *ngIf="signupAllowed" class="signup-link"> | 72 | <ng-container *ngIf="signupAllowed"> |
54 | <span>·</span> | 73 | <span>·</span> |
55 | <a i18n routerLink="/signup" class="create-an-account">Create an account</a> | 74 | <a i18n routerLink="/signup" class="link-orange">Create an account</a> |
56 | </div> | 75 | </ng-container> |
57 | </div> | ||
58 | |||
59 | <div class="looking-for-account alert alert-info" role="alert"> | ||
60 | <h6 class="alert-heading" i18n> | ||
61 | Logging into an account lets you publish content | ||
62 | </h6> | ||
63 | |||
64 | <div *ngIf="signupAllowed" i18n> | ||
65 | This instance allows registration. However, be careful to check the <a class="terms-anchor" (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a><a class="terms-link" target="_blank" routerLink="/about/instance" fragment="terms">Terms</a> before creating an account. | ||
66 | You may also search for another instance to match your exact needs at: <br /><a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. | ||
67 | </div> | ||
68 | |||
69 | <div *ngIf="!signupAllowed" i18n> | ||
70 | Currently this instance doesn't allow for user registration, you may check the <a (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a> for more details or find an instance that gives you the possibility to sign up for an account and upload your videos there. | ||
71 | Find yours among multiple instances at: <br /> <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. | ||
72 | </div> | ||
73 | </div> | 76 | </div> |
74 | </form> | 77 | </form> |
75 | 78 | ||
@@ -86,6 +89,7 @@ | |||
86 | 89 | ||
87 | <div #instanceInformation class="instance-information"> | 90 | <div #instanceInformation class="instance-information"> |
88 | <my-instance-about-accordion | 91 | <my-instance-about-accordion |
92 | [displayInstanceName]="false" | ||
89 | (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels" | 93 | (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels" |
90 | pluginScope="login" pluginHook="filter:login.instance-about-plugin-panels.create.result" | 94 | pluginScope="login" pluginHook="filter:login.instance-about-plugin-panels.create.result" |
91 | ></my-instance-about-accordion> | 95 | ></my-instance-about-accordion> |
diff --git a/client/src/app/+login/login.component.scss b/client/src/app/+login/login.component.scss index 49c873cd4..d31d428f7 100644 --- a/client/src/app/+login/login.component.scss +++ b/client/src/app/+login/login.component.scss | |||
@@ -6,11 +6,13 @@ | |||
6 | 6 | ||
7 | label { | 7 | label { |
8 | display: block; | 8 | display: block; |
9 | font-size: 18px; | ||
10 | margin-bottom: 5px; | ||
9 | } | 11 | } |
10 | 12 | ||
11 | input[type=text], | 13 | input[type=text], |
12 | input[type=email] { | 14 | input[type=email] { |
13 | @include peertube-input-text(340px); | 15 | @include peertube-input-text(100%); |
14 | } | 16 | } |
15 | 17 | ||
16 | .modal-body { | 18 | .modal-body { |
@@ -21,7 +23,7 @@ input[type=email] { | |||
21 | } | 23 | } |
22 | } | 24 | } |
23 | 25 | ||
24 | @media screen and (max-width: #{map-get($container-max-widths, sm)}) { | 26 | @media screen and (max-width: $small-view) { |
25 | .modal-body { | 27 | .modal-body { |
26 | #forgot-password-email { | 28 | #forgot-password-email { |
27 | width: 100%; | 29 | width: 100%; |
@@ -33,114 +35,102 @@ input[type=email] { | |||
33 | } | 35 | } |
34 | } | 36 | } |
35 | 37 | ||
36 | .create-an-account, | ||
37 | .forgot-password-button { | ||
38 | color: pvar(--mainForegroundColor); | ||
39 | cursor: pointer; | ||
40 | transition: opacity cubic-bezier(0.39, 0.575, 0.565, 1); | ||
41 | |||
42 | &:hover { | ||
43 | text-decoration: none !important; | ||
44 | opacity: .7 !important; | ||
45 | } | ||
46 | } | ||
47 | |||
48 | .wrapper { | 38 | .wrapper { |
49 | display: flex; | 39 | display: flex; |
50 | justify-content: space-around; | 40 | justify-content: space-between; |
51 | flex-wrap: wrap; | 41 | flex-wrap: wrap; |
42 | margin: auto; | ||
52 | 43 | ||
53 | > div { | 44 | > div { |
54 | flex: 1 1; | 45 | flex: 1 1; |
55 | } | 46 | } |
56 | 47 | ||
57 | .login-form-and-externals { | 48 | form { |
58 | @include margin-left(10px); | 49 | width: 100%; |
59 | @include margin-right(10px); | 50 | } |
51 | } | ||
60 | 52 | ||
61 | display: flex; | 53 | .wrapper, |
62 | flex-wrap: wrap; | 54 | .alert { |
63 | font-size: 15px; | 55 | max-width: 1200px; |
64 | max-width: 450px; | 56 | } |
65 | margin-bottom: 40px; | ||
66 | 57 | ||
67 | form { | 58 | .alert { |
68 | margin: 0; | 59 | margin: 0 auto 30px; |
60 | } | ||
69 | 61 | ||
70 | &, | 62 | .login-form-and-externals { |
71 | input { | 63 | @include margin-left(10px); |
72 | width: 100%; | 64 | @include margin-right(10px); |
73 | } | ||
74 | 65 | ||
75 | .additionnal-links { | 66 | display: flex; |
76 | display: block; | 67 | flex-wrap: wrap; |
77 | text-align: center; | 68 | justify-content: center; |
78 | margin-top: 20px; | 69 | max-width: 450px; |
79 | margin-bottom: 20px; | 70 | margin-bottom: 40px; |
80 | 71 | ||
81 | .forgot-password-button, | 72 | form { |
82 | .create-an-account { | 73 | margin: 0; |
83 | padding: 4px; | ||
84 | display: inline-block; | ||
85 | 74 | ||
86 | color: var(--mainColor); | 75 | input[type=submit] { |
76 | width: 100%; | ||
77 | } | ||
87 | 78 | ||
88 | &:hover, | 79 | .additional-links { |
89 | &:active { | 80 | display: flex; |
90 | color: var(--mainHoverColor); | 81 | justify-content: center; |
91 | } | 82 | margin: 20px 0 30px; |
92 | } | 83 | |
84 | .link-orange { | ||
85 | margin: 0 15px; | ||
93 | } | 86 | } |
94 | } | 87 | } |
88 | } | ||
89 | } | ||
95 | 90 | ||
96 | .external-login-blocks { | 91 | .external-login-blocks { |
97 | min-width: 200px; | 92 | min-width: 200px; |
93 | text-align: center; | ||
98 | 94 | ||
99 | .block-title { | 95 | .block-title { |
100 | font-weight: $font-semibold; | 96 | font-weight: $font-semibold; |
101 | } | 97 | } |
102 | 98 | ||
103 | .external-login-block { | 99 | .external-login-block { |
104 | @include disable-default-a-behaviour; | 100 | @include disable-default-a-behaviour; |
105 | |||
106 | cursor: pointer; | ||
107 | border: 1px solid #d1d7e0; | ||
108 | border-radius: 5px; | ||
109 | color: pvar(--mainForegroundColor); | ||
110 | margin: 10px 10px 0 0; | ||
111 | display: flex; | ||
112 | justify-content: center; | ||
113 | align-items: center; | ||
114 | min-height: 35px; | ||
115 | min-width: 100px; | ||
116 | |||
117 | &:hover { | ||
118 | background-color: rgba(209, 215, 224, 0.5); | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | 101 | ||
123 | .signup-link { | 102 | cursor: pointer; |
124 | display: inline-block; | 103 | border: 1px solid #d1d7e0; |
104 | border-radius: 5px; | ||
105 | color: pvar(--mainForegroundColor); | ||
106 | margin: 10px 10px 0 0; | ||
107 | display: flex; | ||
108 | justify-content: center; | ||
109 | align-items: center; | ||
110 | min-height: 35px; | ||
111 | min-width: 100px; | ||
112 | |||
113 | &:hover { | ||
114 | background-color: rgba(209, 215, 224, 0.5); | ||
125 | } | 115 | } |
126 | } | 116 | } |
117 | } | ||
127 | 118 | ||
128 | .instance-information { | 119 | .instance-information { |
129 | @include margin-left(10px); | 120 | @include margin-left(10px); |
130 | @include margin-right(10px); | 121 | @include margin-right(10px); |
131 | 122 | ||
132 | max-width: 600px; | 123 | max-width: 600px; |
133 | min-width: 350px; | 124 | min-width: 350px; |
134 | margin-bottom: 40px; | 125 | margin-bottom: 40px; |
135 | } | 126 | } |
136 | 127 | ||
137 | .terms-anchor { | 128 | .terms-anchor { |
138 | display: inline; | 129 | display: inline; |
139 | } | 130 | } |
140 | 131 | ||
141 | .terms-link { | 132 | .terms-link { |
142 | display: none; | 133 | display: none; |
143 | } | ||
144 | } | 134 | } |
145 | 135 | ||
146 | @mixin column-reverse-display { | 136 | @mixin column-reverse-display { |
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index 96754b782..2ed9be16c 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts | |||
@@ -59,6 +59,10 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
59 | return this.serverConfig.signup.allowed === true | 59 | return this.serverConfig.signup.allowed === true |
60 | } | 60 | } |
61 | 61 | ||
62 | get instanceName () { | ||
63 | return this.serverConfig.instance.name | ||
64 | } | ||
65 | |||
62 | onTermsClick (event: Event, instanceInformation: HTMLElement) { | 66 | onTermsClick (event: Event, instanceInformation: HTMLElement) { |
63 | event.preventDefault() | 67 | event.preventDefault() |
64 | 68 | ||
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html index 3751747a9..b557fb011 100644 --- a/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html +++ b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html | |||
@@ -3,42 +3,41 @@ | |||
3 | <div class="margin-content"> | 3 | <div class="margin-content"> |
4 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | 4 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> |
5 | 5 | ||
6 | <div class="form-row"> <!-- channel grid --> | 6 | <div class="row"> <!-- channel grid --> |
7 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 7 | <div class="col-12 col-lg-4 col-xl-3"> |
8 | <div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div> | 8 | <div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div> |
9 | <div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div> | 9 | <div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div> |
10 | </div> | 10 | </div> |
11 | 11 | ||
12 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 12 | <div class="col-12 col-lg-8 col-xl-9"> |
13 | <h6 i18n>Banner image of the channel</h6> | 13 | <label i18n>Banner image of the channel</label> |
14 | 14 | ||
15 | <my-actor-banner-edit | 15 | <my-actor-banner-edit |
16 | *ngIf="videoChannel" [previewImage]="isCreation()" | 16 | *ngIf="videoChannel" [previewImage]="isCreation()" |
17 | [actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()" | 17 | [actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()" |
18 | ></my-actor-banner-edit> | 18 | ></my-actor-banner-edit> |
19 | 19 | ||
20 | <my-actor-avatar-edit | 20 | <my-actor-avatar-edit |
21 | *ngIf="videoChannel" [previewImage]="isCreation()" | 21 | *ngIf="videoChannel" [previewImage]="isCreation()" |
22 | [actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()" | 22 | [actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()" |
23 | [displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()" | 23 | [displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()" |
24 | ></my-actor-avatar-edit> | 24 | ></my-actor-avatar-edit> |
25 | 25 | ||
26 | <div class="form-group" *ngIf="isCreation()"> | 26 | <div class="form-group" *ngIf="isCreation()"> |
27 | <label i18n for="name">Name</label> | 27 | <label i18n for="name">Name</label> |
28 | |||
28 | <div class="input-group"> | 29 | <div class="input-group"> |
29 | <input | 30 | <input |
30 | type="text" id="name" i18n-placeholder placeholder="Example: my_channel" | 31 | type="text" id="name" i18n-placeholder placeholder="Example: my_channel" |
31 | formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" class="form-control" | 32 | formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" class="form-control" |
32 | > | 33 | > |
33 | <div class="input-group-append"> | 34 | <div class="input-group-text">@{{ instanceHost }}</div> |
34 | <span class="input-group-text">@{{ instanceHost }}</span> | ||
35 | </div> | ||
36 | </div> | 35 | </div> |
37 | <div *ngIf="formErrors['name']" class="form-error"> | 36 | <div *ngIf="formErrors['name']" class="form-error"> |
38 | {{ formErrors['name'] }} | 37 | {{ formErrors['name'] }} |
39 | </div> | 38 | </div> |
40 | </div> | 39 | </div> |
41 | 40 | ||
42 | <div class="form-group"> | 41 | <div class="form-group"> |
43 | <label i18n for="display-name">Display name</label> | 42 | <label i18n for="display-name">Display name</label> |
44 | <input | 43 | <input |
@@ -49,7 +48,7 @@ | |||
49 | {{ formErrors['display-name'] }} | 48 | {{ formErrors['display-name'] }} |
50 | </div> | 49 | </div> |
51 | </div> | 50 | </div> |
52 | 51 | ||
53 | <div class="form-group"> | 52 | <div class="form-group"> |
54 | <label i18n for="description">Description</label> | 53 | <label i18n for="description">Description</label> |
55 | <textarea | 54 | <textarea |
@@ -60,37 +59,35 @@ | |||
60 | {{ formErrors.description }} | 59 | {{ formErrors.description }} |
61 | </div> | 60 | </div> |
62 | </div> | 61 | </div> |
63 | 62 | ||
64 | <div class="form-group"> | 63 | <div class="form-group"> |
65 | <label for="support">Support</label> | 64 | <label for="support">Support</label> |
66 | <my-help | 65 | <my-help |
67 | helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support the channel (membership platform...).<br /><br /> | 66 | helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support the channel (membership platform...).<br /><br /> |
68 | When a video is uploaded in this channel, the video support field will be automatically filled by this text." | 67 | When a video is uploaded in this channel, the video support field will be automatically filled by this text." |
69 | ></my-help> | 68 | ></my-help> |
69 | |||
70 | <my-markdown-textarea | 70 | <my-markdown-textarea |
71 | id="support" formControlName="support" textareaMaxWidth="500px" markdownType="enhanced" | 71 | id="support" formControlName="support" markdownType="enhanced" |
72 | [classes]="{ 'input-error': formErrors['support'] }" | 72 | [formError]="formErrors['support']" |
73 | ></my-markdown-textarea> | 73 | ></my-markdown-textarea> |
74 | <div *ngIf="formErrors.support" class="form-error"> | ||
75 | {{ formErrors.support }} | ||
76 | </div> | ||
77 | </div> | 74 | </div> |
78 | 75 | ||
79 | <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()"> | 76 | <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()"> |
80 | <my-peertube-checkbox | 77 | <my-peertube-checkbox |
81 | inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate" | 78 | inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate" |
82 | i18n-labelText labelText="Overwrite support field of all videos of this channel" | 79 | i18n-labelText labelText="Overwrite support field of all videos of this channel" |
83 | ></my-peertube-checkbox> | 80 | ></my-peertube-checkbox> |
84 | </div> | 81 | </div> |
85 | 82 | ||
86 | </div> | 83 | </div> |
87 | </div> | 84 | </div> |
88 | 85 | ||
89 | <div class="form-row"> <!-- submit placement block --> | 86 | <div class="row"> <!-- submit placement block --> |
90 | <div class="col-md-7 col-xl-5"></div> | 87 | <div class="col-md-7 col-xl-5"></div> |
91 | <div class="col-md-5 col-xl-5 d-inline-flex"> | 88 | <div class="col-md-5 col-xl-5 d-inline-flex"> |
92 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> | 89 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> |
93 | </div> | 90 | </div> |
94 | </div> | 91 | </div> |
95 | </form> | 92 | </form> |
96 | </div> \ No newline at end of file | 93 | </div> |
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss index d010d6277..cde848da6 100644 --- a/client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss +++ b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss | |||
@@ -5,11 +5,6 @@ | |||
5 | padding-top: 20px; | 5 | padding-top: 20px; |
6 | } | 6 | } |
7 | 7 | ||
8 | label { | ||
9 | font-weight: $font-regular; | ||
10 | font-size: 100%; | ||
11 | } | ||
12 | |||
13 | .video-channel-title { | 8 | .video-channel-title { |
14 | @include settings-big-title; | 9 | @include settings-big-title; |
15 | } | 10 | } |
@@ -24,31 +19,21 @@ my-actor-banner-edit { | |||
24 | max-width: 500px; | 19 | max-width: 500px; |
25 | } | 20 | } |
26 | 21 | ||
27 | .input-group { | 22 | input[type=text] { |
28 | @include peertube-input-group(fit-content); | 23 | @include peertube-input-text(340px); |
29 | } | ||
30 | 24 | ||
31 | .input-group-append { | 25 | display: block; |
32 | height: 30px; | ||
33 | } | ||
34 | |||
35 | input { | ||
36 | &[type=text] { | ||
37 | @include peertube-input-text(340px); | ||
38 | |||
39 | display: block; | ||
40 | 26 | ||
41 | &#name { | 27 | &#name { |
42 | width: auto; | 28 | width: auto; |
43 | flex-grow: 1; | 29 | flex-grow: 1; |
44 | } | ||
45 | } | 30 | } |
31 | } | ||
46 | 32 | ||
47 | &[type=submit] { | 33 | input[type=submit] { |
48 | @include peertube-button; | 34 | @include peertube-button; |
49 | @include orange-button; | 35 | @include orange-button; |
50 | @include margin-left(auto); | 36 | @include margin-left(auto); |
51 | } | ||
52 | } | 37 | } |
53 | 38 | ||
54 | textarea { | 39 | textarea { |
@@ -57,6 +42,11 @@ textarea { | |||
57 | display: block; | 42 | display: block; |
58 | } | 43 | } |
59 | 44 | ||
45 | my-markdown-textarea { | ||
46 | display: block; | ||
47 | max-width: 500px; | ||
48 | } | ||
49 | |||
60 | .peertube-select-container { | 50 | .peertube-select-container { |
61 | @include peertube-select-container(340px); | 51 | @include peertube-select-container(340px); |
62 | } | 52 | } |
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.html b/client/src/app/+my-account/my-account-applications/my-account-applications.component.html index 68d094a4f..2fc691707 100644 --- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.html +++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.html | |||
@@ -3,8 +3,9 @@ | |||
3 | <ng-container i18n>Applications</ng-container> | 3 | <ng-container i18n>Applications</ng-container> |
4 | </h1> | 4 | </h1> |
5 | 5 | ||
6 | <div class="form-row"> <!-- built-in token grid --> | 6 | <div class="row"> <!-- built-in token grid --> |
7 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 7 | |
8 | <div class="group col-12 col-lg-4 col-xl-3"> | ||
8 | <h2 i18n class="applications-title">SUBSCRIPTION FEED</h2> | 9 | <h2 i18n class="applications-title">SUBSCRIPTION FEED</h2> |
9 | <div i18n class="applications-description"> | 10 | <div i18n class="applications-description"> |
10 | Use third-party feed aggregators to retrieve the list of videos from | 11 | Use third-party feed aggregators to retrieve the list of videos from |
@@ -12,16 +13,16 @@ | |||
12 | </div> | 13 | </div> |
13 | </div> | 14 | </div> |
14 | 15 | ||
15 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 16 | <div class="col-12 col-lg-8 col-xl-9"> |
16 | 17 | ||
17 | <div class="form-group"> | 18 | <div class="form-group"> |
18 | <label i18n for="feed-url">Feed URL</label> | 19 | <label i18n for="feed-url">Feed URL</label> |
19 | <my-input-toggle-hidden [value]="feedUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | 20 | <my-input-text [value]="feedUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> |
20 | </div> | 21 | </div> |
21 | 22 | ||
22 | <div class="form-group"> | 23 | <div class="form-group"> |
23 | <label i18n for="feed-token">Feed Token</label> | 24 | <label i18n for="feed-token">Feed Token</label> |
24 | <my-input-toggle-hidden [value]="feedToken" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden> | 25 | <my-input-text [value]="feedToken" [withCopy]="true" [readonly]="true"></my-input-text> |
25 | 26 | ||
26 | <div class="form-group-description" i18n>⚠️ Never share your feed token with anyone.</div> | 27 | <div class="form-group-description" i18n>⚠️ Never share your feed token with anyone.</div> |
27 | </div> | 28 | </div> |
@@ -29,7 +30,7 @@ | |||
29 | </div> | 30 | </div> |
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | <div class="form-row mt-4"> <!-- submit placement block --> | 33 | <div class="row mt-4"> <!-- submit placement block --> |
33 | <div class="col-md-7 col-xl-5"></div> | 34 | <div class="col-md-7 col-xl-5"></div> |
34 | <div class="col-md-5 col-xl-5"> | 35 | <div class="col-md-5 col-xl-5"> |
35 | <input (click)="renewToken()" type="submit" i18n-value value="Renew token"> | 36 | <input (click)="renewToken()" type="submit" i18n-value value="Renew token"> |
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss b/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss index 2a3b85c22..b3fedd2f6 100644 --- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss +++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss | |||
@@ -1,11 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | .applications-title { | 4 | .applications-title { |
10 | @include settings-big-title; | 5 | @include settings-big-title; |
11 | } | 6 | } |
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html index f0e9f4010..b98cd1156 100644 --- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html | |||
@@ -1,11 +1,11 @@ | |||
1 | <h1 class="sr-only" i18n>Notifications</h1> | 1 | <h1 class="visually-hidden" i18n>Notifications</h1> |
2 | <div class="header"> | 2 | <div class="header"> |
3 | <a routerLink="/my-account/settings" fragment="notifications" i18n> | 3 | <a routerLink="/my-account/settings" fragment="notifications" i18n> |
4 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> | 4 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> |
5 | Notification preferences | 5 | Notification preferences |
6 | </a> | 6 | </a> |
7 | 7 | ||
8 | <div class="peertube-select-container peertube-select-button ml-2 mr-2"> | 8 | <div class="peertube-select-container peertube-select-button ms-2 me-2"> |
9 | <select [(ngModel)]="notificationSortType" (ngModelChange)="onChangeSortColumn()" class="form-control"> | 9 | <select [(ngModel)]="notificationSortType" (ngModelChange)="onChangeSortColumn()" class="form-control"> |
10 | <option value="undefined" disabled>Sort by</option> | 10 | <option value="undefined" disabled>Sort by</option> |
11 | <option value="createdAt" i18n>Newest first</option> | 11 | <option value="createdAt" i18n>Newest first</option> |
@@ -13,7 +13,7 @@ | |||
13 | </select> | 13 | </select> |
14 | </div> | 14 | </div> |
15 | 15 | ||
16 | <button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> | 16 | <button class="btn ms-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> |
17 | <ng-container *ngIf="hasUnreadNotifications()"> | 17 | <ng-container *ngIf="hasUnreadNotifications()"> |
18 | <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon> | 18 | <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon> |
19 | 19 | ||
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss index b0ac45e30..d412e568f 100644 --- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | .header { | 4 | .header { |
5 | display: flex; | 5 | display: flex; |
6 | font-size: 15px; | ||
7 | margin-bottom: 20px; | 6 | margin-bottom: 20px; |
8 | 7 | ||
9 | a { | 8 | a { |
@@ -23,11 +22,6 @@ | |||
23 | } | 22 | } |
24 | } | 23 | } |
25 | 24 | ||
26 | |||
27 | my-user-notifications { | ||
28 | font-size: 15px; | ||
29 | } | ||
30 | |||
31 | @media screen and (max-width: $mobile-view) { | 25 | @media screen and (max-width: $mobile-view) { |
32 | .header { | 26 | .header { |
33 | flex-direction: column; | 27 | flex-direction: column; |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html index 6d2d7d46e..d85be846b 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html | |||
@@ -1,18 +1,20 @@ | |||
1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
2 | <div *ngIf="success" class="alert alert-success">{{ success }}</div> | 2 | <div *ngIf="success" class="alert alert-success">{{ success }}</div> |
3 | 3 | ||
4 | <div i18n class="current-email"> | ||
5 | Your current email is <span class="email">{{ user.email }}</span>. | ||
6 | It is never shown to the public. | ||
7 | </div> | ||
8 | |||
9 | <div i18n class="pending-email" *ngIf="user.pendingEmail"> | 4 | <div i18n class="pending-email" *ngIf="user.pendingEmail"> |
10 | <span class="email">{{ user.pendingEmail }}</span> is awaiting email verification | 5 | <strong>{{ user.pendingEmail }}</strong> is awaiting email verification |
11 | </div> | 6 | </div> |
12 | 7 | ||
13 | <form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form" *ngIf="user.pluginAuth === null"> | 8 | <form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form" *ngIf="user.pluginAuth === null"> |
14 | 9 | ||
15 | <div class="form-group"> | 10 | <div class="form-group"> |
11 | <label i18n for="new-email">Change your email</label> | ||
12 | |||
13 | <div i18n class="form-group-description"> | ||
14 | Your current email is <strong>{{ user.email }}</strong>. | ||
15 | It is never shown to the public. | ||
16 | </div> | ||
17 | |||
16 | <input | 18 | <input |
17 | type="email" id="new-email" i18n-placeholder placeholder="New email" class="form-control" | 19 | type="email" id="new-email" i18n-placeholder placeholder="New email" class="form-control" |
18 | formControlName="new-email" [ngClass]="{ 'input-error': formErrors['new-email'] }" | 20 | formControlName="new-email" [ngClass]="{ 'input-error': formErrors['new-email'] }" |
@@ -23,14 +25,10 @@ | |||
23 | </div> | 25 | </div> |
24 | 26 | ||
25 | <div class="form-group"> | 27 | <div class="form-group"> |
26 | <my-input-toggle-hidden formControlName="password" | 28 | <my-input-text |
27 | id="password" | 29 | formControlName="password" id="password" i18n-placeholder placeholder="Current password" |
28 | i18n-placeholder placeholder="Current password" | 30 | [formError]="formErrors['password']" autocomplete="current-password" |
29 | [ngClass]="{ 'input-error': formErrors['password'] }" | 31 | ></my-input-text> |
30 | autocomplete="current-password"></my-input-toggle-hidden> | ||
31 | <div *ngIf="formErrors['password']" class="form-error"> | ||
32 | {{ formErrors['password'] }} | ||
33 | </div> | ||
34 | </div> | 32 | </div> |
35 | 33 | ||
36 | <input type="submit" i18n-value value="Change email" [disabled]="!form.valid"> | 34 | <input type="submit" i18n-value value="Change email" [disabled]="!form.valid"> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss index 788db02ad..8d1804a93 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss | |||
@@ -1,18 +1,17 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | 4 | form { |
5 | font-weight: $font-regular; | 5 | max-width: 340px; |
6 | font-size: 100%; | ||
7 | } | 6 | } |
8 | 7 | ||
9 | my-input-toggle-hidden { | 8 | my-input-text { |
10 | width: 340px; | ||
11 | display: block; | 9 | display: block; |
10 | width: 100%; | ||
12 | } | 11 | } |
13 | 12 | ||
14 | input[type=email] { | 13 | input[type=email] { |
15 | @include peertube-input-text(340px); | 14 | @include peertube-input-text(100%); |
16 | 15 | ||
17 | display: block; | 16 | display: block; |
18 | } | 17 | } |
@@ -22,15 +21,6 @@ input[type=submit] { | |||
22 | @include orange-button; | 21 | @include orange-button; |
23 | } | 22 | } |
24 | 23 | ||
25 | .current-email, | ||
26 | .pending-email { | 24 | .pending-email { |
27 | margin-bottom: 15px; | 25 | margin-bottom: 15px; |
28 | |||
29 | .email { | ||
30 | font-weight: $font-semibold; | ||
31 | } | ||
32 | } | ||
33 | |||
34 | .form-group { | ||
35 | width: max-content; | ||
36 | } | 26 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html index 43facb7ed..f961d3294 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html | |||
@@ -3,32 +3,20 @@ | |||
3 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> | 3 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> |
4 | 4 | ||
5 | <label i18n for="current-password">Change password</label> | 5 | <label i18n for="current-password">Change password</label> |
6 | <my-input-toggle-hidden formControlName="current-password" | 6 | <my-input-text |
7 | inputId="current-password" | 7 | formControlName="current-password" inputId="current-password" i18n-placeholder placeholder="Current password" |
8 | i18n-placeholder placeholder="Current password" | 8 | [formError]="formErrors['current-password']" autocomplete="current-password" |
9 | [ngClass]="{ 'input-error': formErrors['current-password'] }" | 9 | ></my-input-text> |
10 | autocomplete="current-password"></my-input-toggle-hidden> | ||
11 | <div *ngIf="formErrors['current-password']" class="form-error"> | ||
12 | {{ formErrors['current-password'] }} | ||
13 | </div> | ||
14 | 10 | ||
15 | <my-input-toggle-hidden formControlName="new-password" | 11 | <my-input-text |
16 | inputId="new-password" | 12 | formControlName="new-password" inputId="new-password" i18n-placeholder placeholder="New password" |
17 | i18n-placeholder placeholder="New password" | 13 | [formError]="formErrors['new-password']" autocomplete="new-password" |
18 | [ngClass]="{ 'input-error': formErrors['new-password'] }" | 14 | ></my-input-text> |
19 | autocomplete="new-password"></my-input-toggle-hidden> | ||
20 | <div *ngIf="formErrors['new-password']" class="form-error"> | ||
21 | {{ formErrors['new-password'] }} | ||
22 | </div> | ||
23 | 15 | ||
24 | <my-input-toggle-hidden formControlName="new-confirmed-password" | 16 | <my-input-text |
25 | inputId="new-confirmed-password" | 17 | formControlName="new-confirmed-password" inputId="new-confirmed-password" i18n-placeholder placeholder="Confirm new password" |
26 | i18n-placeholder placeholder="Confirm new password" | 18 | [formError]="formErrors['new-confirmed-password']" autocomplete="new-password" |
27 | [ngClass]="{ 'input-error': formErrors['new-confirmed-password'] }" | 19 | ></my-input-text> |
28 | autocomplete="new-password"></my-input-toggle-hidden> | ||
29 | <div *ngIf="formErrors['new-confirmed-password']" class="form-error"> | ||
30 | {{ formErrors['new-confirmed-password'] }} | ||
31 | </div> | ||
32 | 20 | ||
33 | <input type="submit" i18n-value value="Change password" [disabled]="!form.valid"> | 21 | <input type="submit" i18n-value value="Change password" [disabled]="!form.valid"> |
34 | </form> | 22 | </form> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss index c46aae266..a29f04c36 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.scss | |||
@@ -1,17 +1,16 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | 4 | form { |
5 | font-weight: $font-regular; | 5 | max-width: 340px; |
6 | font-size: 100%; | ||
7 | } | 6 | } |
8 | 7 | ||
9 | my-input-toggle-hidden { | 8 | my-input-text { |
10 | width: 340px; | ||
11 | display: block; | 9 | display: block; |
10 | width: 100%; | ||
12 | } | 11 | } |
13 | 12 | ||
14 | my-input-toggle-hidden + my-input-toggle-hidden { | 13 | my-input-text + my-input-text { |
15 | margin-top: 15px; | 14 | margin-top: 15px; |
16 | } | 15 | } |
17 | 16 | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html index c3cfe0314..46f5e5d6b 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html | |||
@@ -1,13 +1,13 @@ | |||
1 | <div *ngIf="webNotifications"> | 1 | <div *ngIf="webNotifications"> |
2 | <ng-container *ngFor="let group of notificationSettingGroups"> | 2 | <ng-container *ngFor="let group of notificationSettingGroups"> |
3 | <div class="header custom-row"> | 3 | <div class="header notification-row"> |
4 | <div i18n>{{ group.label }}</div> | 4 | <div i18n>{{ group.label }}</div> |
5 | <div i18n>Web</div> | 5 | <div i18n>Web</div> |
6 | <div i18n *ngIf="emailEnabled">Email</div> | 6 | <div i18n *ngIf="emailEnabled">Email</div> |
7 | </div> | 7 | </div> |
8 | 8 | ||
9 | <ng-container *ngFor="let notificationType of group.keys"> | 9 | <ng-container *ngFor="let notificationType of group.keys"> |
10 | <div class="custom-row" *ngIf="hasUserRight(notificationType)"> | 10 | <div class="small notification-row" *ngIf="hasUserRight(notificationType)"> |
11 | <div>{{ labelNotifications[notificationType] }}</div> | 11 | <div>{{ labelNotifications[notificationType] }}</div> |
12 | 12 | ||
13 | <div> | 13 | <div> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss index 2fe1f9536..8181c3175 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss | |||
@@ -1,13 +1,12 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .custom-row { | 4 | .notification-row { |
5 | display: flex; | 5 | display: flex; |
6 | align-items: center; | 6 | align-items: center; |
7 | border-bottom: 1px solid $separator-border-color; | 7 | border-bottom: 1px solid $separator-border-color; |
8 | 8 | ||
9 | &.header { | 9 | &.header { |
10 | font-size: 16px; | ||
11 | font-weight: $font-semibold; | 10 | font-weight: $font-semibold; |
12 | margin-top: 10px; | 11 | margin-top: 10px; |
13 | } | 12 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts index 7c13282fa..769ab647a 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts | |||
@@ -37,7 +37,7 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
37 | myVideoPublished: $localize`Video published (after transcoding/scheduled update)`, | 37 | myVideoPublished: $localize`Video published (after transcoding/scheduled update)`, |
38 | myVideoImportFinished: $localize`Video import finished`, | 38 | myVideoImportFinished: $localize`Video import finished`, |
39 | newUserRegistration: $localize`A new user registered on your instance`, | 39 | newUserRegistration: $localize`A new user registered on your instance`, |
40 | newFollow: $localize`You or your channel(s) has a new follower`, | 40 | newFollow: $localize`You or one of your channels has a new follower`, |
41 | commentMention: $localize`Someone mentioned you in video comments`, | 41 | commentMention: $localize`Someone mentioned you in video comments`, |
42 | newInstanceFollower: $localize`Your instance has a new follower`, | 42 | newInstanceFollower: $localize`Your instance has a new follower`, |
43 | autoInstanceFollowing: $localize`Your instance automatically followed another instance`, | 43 | autoInstanceFollowing: $localize`Your instance automatically followed another instance`, |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html index ae5f25cff..2b192ab6d 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.html | |||
@@ -8,7 +8,7 @@ | |||
8 | type="text" id="username" class="form-control" | 8 | type="text" id="username" class="form-control" |
9 | formControlName="username" readonly | 9 | formControlName="username" readonly |
10 | > | 10 | > |
11 | <div class="muted" i18n> | 11 | <div class="form-group-description" i18n> |
12 | People can find you using @{{ user.username }}@{{ instanceHost }} | 12 | People can find you using @{{ user.username }}@{{ instanceHost }} |
13 | </div> | 13 | </div> |
14 | </div> | 14 | </div> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss index cbac81d01..23e701332 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss +++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.scss | |||
@@ -1,15 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | .form-group:first-child { | ||
10 | margin-bottom: 15px; | ||
11 | } | ||
12 | |||
13 | input#username + .muted { | 4 | input#username + .muted { |
14 | margin-top: 5px; | 5 | margin-top: 5px; |
15 | } | 6 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html index 8ca197fd4..d9e833019 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html | |||
@@ -1,83 +1,83 @@ | |||
1 | <h1 class="sr-only" i18n>Settings</h1> | 1 | <h1 class="visually-hidden" i18n>Settings</h1> |
2 | <div class="form-row"> <!-- preview --> | ||
3 | <div class="form-group col-12 col-lg-4 col-xl-3"></div> | ||
4 | 2 | ||
5 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 3 | <div class="row"> <!-- preview --> |
4 | <div class="col-12 col-lg-4 col-xl-3"></div> | ||
5 | |||
6 | <div class="col-12 col-lg-8 col-xl-9"> | ||
6 | <my-actor-avatar-edit [actor]="user.account" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"></my-actor-avatar-edit> | 7 | <my-actor-avatar-edit [actor]="user.account" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"></my-actor-avatar-edit> |
7 | </div> | 8 | </div> |
8 | </div> | 9 | </div> |
9 | 10 | ||
10 | <div class="form-row"> <!-- profile settings grid --> | 11 | <div class="row mt-3"> <!-- profile settings grid --> |
11 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 12 | <div class="col-12 col-lg-4 col-xl-3"> |
12 | <h2 i18n class="account-title">PROFILE SETTINGS</h2> | 13 | <h2 i18n class="account-title">PROFILE SETTINGS</h2> |
13 | </div> | 14 | </div> |
14 | 15 | ||
15 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 16 | <div class="col-12 col-lg-8 col-xl-9"> |
16 | |||
17 | <my-user-quota [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota> | 17 | <my-user-quota [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota> |
18 | 18 | ||
19 | <my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile> | 19 | <my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile> |
20 | </div> | 20 | </div> |
21 | </div> | 21 | </div> |
22 | 22 | ||
23 | <div class="form-row mt-5"> <!-- interface grid --> | 23 | <div class="row mt-5"> <!-- interface grid --> |
24 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 24 | <div class="col-12 col-lg-4 col-xl-3"> |
25 | <h2 i18n class="account-title">INTERFACE</h2> | 25 | <h2 i18n class="account-title">INTERFACE</h2> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 28 | <div class="col-12 col-lg-8 col-xl-9"> |
29 | <my-user-interface-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-interface-settings> | 29 | <my-user-interface-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-interface-settings> |
30 | </div> | 30 | </div> |
31 | </div> | 31 | </div> |
32 | 32 | ||
33 | <div class="form-row mt-5"> <!-- video settings grid --> | 33 | <div class="row mt-5"> <!-- video settings grid --> |
34 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 34 | <div class="col-12 col-lg-4 col-xl-3"> |
35 | <div class="anchor" id="video-settings"></div> <!-- video settings anchor --> | 35 | <div class="anchor" id="video-settings"></div> <!-- video settings anchor --> |
36 | <h2 i18n class="account-title">VIDEO SETTINGS</h2> | 36 | <h2 i18n class="account-title">VIDEO SETTINGS</h2> |
37 | </div> | 37 | </div> |
38 | 38 | ||
39 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 39 | <div class="col-12 col-lg-8 col-xl-9"> |
40 | <my-user-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-video-settings> | 40 | <my-user-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-video-settings> |
41 | </div> | 41 | </div> |
42 | </div> | 42 | </div> |
43 | 43 | ||
44 | <div class="form-row mt-5"> <!-- notifications grid --> | 44 | <div class="row mt-5"> <!-- notifications grid --> |
45 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 45 | <div class="col-12 col-lg-4 col-xl-3"> |
46 | <div class="anchor" id="notifications"></div> <!-- notifications anchor --> | 46 | <div class="anchor" id="notifications"></div> <!-- notifications anchor --> |
47 | <h2 i18n class="account-title">NOTIFICATIONS</h2> | 47 | <h2 i18n class="account-title">NOTIFICATIONS</h2> |
48 | </div> | 48 | </div> |
49 | 49 | ||
50 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 50 | <div class="col-12 col-lg-8 col-xl-9"> |
51 | <my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences> | 51 | <my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences> |
52 | </div> | 52 | </div> |
53 | </div> | 53 | </div> |
54 | 54 | ||
55 | <div class="form-row mt-5" *ngIf="user.pluginAuth === null"> <!-- password grid --> | 55 | <div class="row mt-5" *ngIf="user.pluginAuth === null"> <!-- password grid --> |
56 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 56 | <div class="col-12 col-lg-4 col-xl-3"> |
57 | <h2 i18n class="account-title">PASSWORD</h2> | 57 | <h2 i18n class="account-title">PASSWORD</h2> |
58 | </div> | 58 | </div> |
59 | 59 | ||
60 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 60 | <div class="col-12 col-lg-8 col-xl-9"> |
61 | <my-account-change-password></my-account-change-password> | 61 | <my-account-change-password></my-account-change-password> |
62 | </div> | 62 | </div> |
63 | </div> | 63 | </div> |
64 | 64 | ||
65 | <div class="form-row mt-5"> <!-- email grid --> | 65 | <div class="row mt-5"> <!-- email grid --> |
66 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 66 | <div class="col-12 col-lg-4 col-xl-3"> |
67 | <h2 i18n class="account-title">EMAIL</h2> | 67 | <h2 i18n class="account-title">EMAIL</h2> |
68 | </div> | 68 | </div> |
69 | 69 | ||
70 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 70 | <div class="col-12 col-lg-8 col-xl-9"> |
71 | <my-account-change-email></my-account-change-email> | 71 | <my-account-change-email></my-account-change-email> |
72 | </div> | 72 | </div> |
73 | </div> | 73 | </div> |
74 | 74 | ||
75 | <div class="form-row mt-5"> <!-- danger zone grid --> | 75 | <div class="row mt-5"> <!-- danger zone grid --> |
76 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 76 | <div class="col-12 col-lg-4 col-xl-3"> |
77 | <h2 i18n class="account-title account-title-danger">DANGER ZONE</h2> | 77 | <h2 i18n class="account-title account-title-danger">DANGER ZONE</h2> |
78 | </div> | 78 | </div> |
79 | 79 | ||
80 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 80 | <div class="col-12 col-lg-8 col-xl-9"> |
81 | <my-account-danger-zone [user]="user"></my-account-danger-zone> | 81 | <my-account-danger-zone [user]="user"></my-account-danger-zone> |
82 | </div> | 82 | </div> |
83 | </div> | 83 | </div> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss b/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss index 1c216d79d..8206f4dd8 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss | |||
@@ -1,5 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | @use '~bootstrap/scss/functions' as *; | ||
3 | 4 | ||
4 | .account-title { | 5 | .account-title { |
5 | @include settings-big-title; | 6 | @include settings-big-title; |
@@ -9,6 +10,6 @@ | |||
9 | } | 10 | } |
10 | } | 11 | } |
11 | 12 | ||
12 | .form-group { | 13 | .row > div { |
13 | max-width: 500px; | 14 | max-width: 500px; |
14 | } | 15 | } |
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html index b465d0156..1c44c8472 100644 --- a/client/src/app/+my-account/my-account.component.html +++ b/client/src/app/+my-account/my-account.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div class="row"> | 1 | <div class="root"> |
2 | <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown> | 2 | <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown> |
3 | 3 | ||
4 | <div class="margin-content pb-5" [ngClass]="{ 'offset-content': !isBroadcastMessageDisplayed }"> | 4 | <div class="margin-content pb-5" [ngClass]="{ 'offset-content': !isBroadcastMessageDisplayed }"> |
diff --git a/client/src/app/+my-account/my-account.component.scss b/client/src/app/+my-account/my-account.component.scss index 1ec25315a..6275b7ac2 100644 --- a/client/src/app/+my-account/my-account.component.scss +++ b/client/src/app/+my-account/my-account.component.scss | |||
@@ -1,7 +1,7 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .row { | 4 | .root { |
5 | @include sub-menu-h1; | 5 | @include sub-menu-h1; |
6 | 6 | ||
7 | flex-direction: column; | 7 | flex-direction: column; |
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html index 89327b065..e942e002b 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <h1> | 1 | <h1> |
2 | <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon> | 2 | <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon> |
3 | <ng-container i18n>My channels</ng-container> | 3 | <ng-container i18n>My channels</ng-container> |
4 | <span class="badge badge-secondary">{{ totalItems }}</span> | 4 | <span *ngIf="totalItems" class="pt-badge badge-secondary">{{ totalItems }}</span> |
5 | </h1> | 5 | </h1> |
6 | 6 | ||
7 | <my-channels-setup-message [hideLink]="true"></my-channels-setup-message> | 7 | <my-channels-setup-message [hideLink]="true"></my-channels-setup-message> |
@@ -19,7 +19,7 @@ | |||
19 | 19 | ||
20 | <div class="video-channels"> | 20 | <div class="video-channels"> |
21 | <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> | 21 | <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> |
22 | <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar> | 22 | <my-actor-avatar [actor]="videoChannel" actorType="channel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar> |
23 | 23 | ||
24 | <div class="video-channel-info"> | 24 | <div class="video-channel-info"> |
25 | <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page"> | 25 | <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page"> |
@@ -31,10 +31,14 @@ | |||
31 | i18n class="video-channel-followers" | 31 | i18n class="video-channel-followers" |
32 | [routerLink]="[ '/my-library', 'followers' ]" [queryParams]="{ search: 'channel:' + videoChannel.name }" | 32 | [routerLink]="[ '/my-library', 'followers' ]" [queryParams]="{ search: 'channel:' + videoChannel.name }" |
33 | > | 33 | > |
34 | {videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}} | 34 | {videoChannel.followersCount, plural, =0 {No subscribers} =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}} |
35 | </a> | 35 | </a> |
36 | 36 | ||
37 | <div i18n class="video-channel-videos">{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}</div> | 37 | <div class="d-flex"> |
38 | <span i18n>{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}</span> | ||
39 | <span class="mx-1">·</span> | ||
40 | <span i18n>{videoChannel.totalViews, plural, =0 {No views} =1 {1 view} other {{{ videoChannel.totalViews | myNumberFormatter }} views}}</span> | ||
41 | </div> | ||
38 | 42 | ||
39 | <div class="video-channel-buttons"> | 43 | <div class="video-channel-buttons"> |
40 | <my-edit-button label [routerLink]="[ '/manage/update', videoChannel.nameWithHost ]"></my-edit-button> | 44 | <my-edit-button label [routerLink]="[ '/manage/update', videoChannel.nameWithHost ]"></my-edit-button> |
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.html b/client/src/app/+my-library/my-follows/my-followers.component.html index a8a3da863..e9b0f6355 100644 --- a/client/src/app/+my-library/my-follows/my-followers.component.html +++ b/client/src/app/+my-library/my-follows/my-followers.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <span> | 2 | <span> |
3 | <my-global-icon iconName="follower" aria-hidden="true"></my-global-icon> | 3 | <my-global-icon iconName="follower" aria-hidden="true"></my-global-icon> |
4 | <ng-container i18n>My followers</ng-container> | 4 | <ng-container i18n>My followers</ng-container> |
5 | <span class="badge badge-secondary"> {{ pagination.totalItems }}</span> | 5 | <span *ngIf="pagination.totalItems" class="pt-badge badge-secondary"> {{ pagination.totalItems }}</span> |
6 | </span> | 6 | </span> |
7 | </h1> | 7 | </h1> |
8 | 8 | ||
@@ -14,15 +14,15 @@ | |||
14 | 14 | ||
15 | <div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> | 15 | <div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> |
16 | <div *ngFor="let follow of follows" class="actor"> | 16 | <div *ngFor="let follow of follows" class="actor"> |
17 | <my-actor-avatar [account]="follow.follower" [href]="follow.follower.url" size="40"></my-actor-avatar> | 17 | <my-actor-avatar [actor]="follow.follower" actorType="account" [href]="follow.follower.url" size="40"></my-actor-avatar> |
18 | 18 | ||
19 | <div class="actor-info"> | 19 | <div class="actor-info"> |
20 | <a [href]="follow.follower.url" class="actor-names" rel="noopener noreferrer" target="_blank" i18n-title title="Follower page"> | 20 | <a [href]="follow.follower.url" class="actor-names" rel="noopener noreferrer" target="_blank" i18n-title title="Follower page"> |
21 | <div class="actor-display-name">{{ follow.follower.name + '@' + follow.follower.host }}</div> | 21 | <div class="actor-display-name">{{ follow.follower.name + '@' + follow.follower.host }}</div> |
22 | <span class="glyphicon glyphicon-new-window"></span> | 22 | <my-global-icon iconName="external-link"></my-global-icon> |
23 | </a> | 23 | </a> |
24 | 24 | ||
25 | <div class="muted"> | 25 | <div class="small muted"> |
26 | <ng-container *ngIf="isFollowingAccount(follow)" i18n>Is following all your channels</ng-container> | 26 | <ng-container *ngIf="isFollowingAccount(follow)" i18n>Is following all your channels</ng-container> |
27 | <ng-container *ngIf="!isFollowingAccount(follow)" i18n>Is following your channel {{ follow.following.name }}</ng-container> | 27 | <ng-container *ngIf="!isFollowingAccount(follow)" i18n>Is following your channel {{ follow.following.name }}</ng-container> |
28 | </div> | 28 | </div> |
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.scss b/client/src/app/+my-library/my-follows/my-followers.component.scss index fae4cd972..2caa4cc48 100644 --- a/client/src/app/+my-library/my-follows/my-followers.component.scss +++ b/client/src/app/+my-library/my-follows/my-followers.component.scss | |||
@@ -13,14 +13,4 @@ input[type=text] { | |||
13 | 13 | ||
14 | .actor { | 14 | .actor { |
15 | @include actor-row($min-height: auto, $separator: true); | 15 | @include actor-row($min-height: auto, $separator: true); |
16 | |||
17 | .actor-display-name { | ||
18 | font-size: 16px; | ||
19 | |||
20 | + .glyphicon { | ||
21 | @include margin-left(5px); | ||
22 | |||
23 | font-size: 12px; | ||
24 | } | ||
25 | } | ||
26 | } | 16 | } |
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.html b/client/src/app/+my-library/my-follows/my-subscriptions.component.html index 391c4d3be..d5f164bf2 100644 --- a/client/src/app/+my-library/my-follows/my-subscriptions.component.html +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <span> | 2 | <span> |
3 | <my-global-icon iconName="subscriptions" aria-hidden="true"></my-global-icon> | 3 | <my-global-icon iconName="subscriptions" aria-hidden="true"></my-global-icon> |
4 | <ng-container i18n>My subscriptions</ng-container> | 4 | <ng-container i18n>My subscriptions</ng-container> |
5 | <span class="badge badge-secondary"> {{ pagination.totalItems }}</span> | 5 | <span *ngIf="pagination.totalItems" class="pt-badge badge-secondary"> {{ pagination.totalItems }}</span> |
6 | </span> | 6 | </span> |
7 | </h1> | 7 | </h1> |
8 | 8 | ||
@@ -14,7 +14,7 @@ | |||
14 | 14 | ||
15 | <div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> | 15 | <div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> |
16 | <div *ngFor="let videoChannel of videoChannels" class="actor"> | 16 | <div *ngFor="let videoChannel of videoChannels" class="actor"> |
17 | <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar> | 17 | <my-actor-avatar [actor]="videoChannel" actorType="channel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar> |
18 | 18 | ||
19 | <div class="actor-info"> | 19 | <div class="actor-info"> |
20 | <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="actor-names" i18n-title title="Channel page"> | 20 | <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="actor-names" i18n-title title="Channel page"> |
@@ -27,7 +27,7 @@ | |||
27 | <a [routerLink]="[ '/a', videoChannel.ownerBy ]" i18n-title title="Owner account page" class="actor-owner"> | 27 | <a [routerLink]="[ '/a', videoChannel.ownerBy ]" i18n-title title="Owner account page" class="actor-owner"> |
28 | <span i18n>Created by {{ videoChannel.ownerBy }}</span> | 28 | <span i18n>Created by {{ videoChannel.ownerBy }}</span> |
29 | 29 | ||
30 | <my-actor-avatar [account]="videoChannel.ownerAccount" size="18"></my-actor-avatar> | 30 | <my-actor-avatar [actor]="videoChannel.ownerAccount" actorType="account" size="18"></my-actor-avatar> |
31 | </a> | 31 | </a> |
32 | </div> | 32 | </div> |
33 | 33 | ||
diff --git a/client/src/app/+my-library/my-history/my-history.component.html b/client/src/app/+my-library/my-history/my-history.component.html index 14bf01804..6791dab52 100644 --- a/client/src/app/+my-library/my-history/my-history.component.html +++ b/client/src/app/+my-library/my-history/my-history.component.html | |||
@@ -1,6 +1,7 @@ | |||
1 | <h1> | 1 | <h1> |
2 | <my-global-icon iconName="history" aria-hidden="true"></my-global-icon> | 2 | <my-global-icon iconName="history" aria-hidden="true"></my-global-icon> |
3 | <ng-container i18n>My watch history</ng-container> <span class="badge badge-secondary">{{ pagination.totalItems }}</span> | 3 | <ng-container i18n>My watch history</ng-container> |
4 | <span *ngIf="pagination.totalItems" class="pt-badge badge-secondary">{{ pagination.totalItems }}</span> | ||
4 | </h1> | 5 | </h1> |
5 | 6 | ||
6 | <div class="top-buttons"> | 7 | <div class="top-buttons"> |
@@ -26,7 +27,7 @@ | |||
26 | [titlePage]="titlePage" | 27 | [titlePage]="titlePage" |
27 | [getVideosObservableFunction]="getVideosObservableFunction" | 28 | [getVideosObservableFunction]="getVideosObservableFunction" |
28 | [user]="user" | 29 | [user]="user" |
29 | i18n-noResultMessage noResultMessage="You don't have any video in your watch history yet." | 30 | [noResultMessage]="getNoResultMessage()" |
30 | [enableSelection]="false" | 31 | [enableSelection]="false" |
31 | [disabled]="disabled" | 32 | [disabled]="disabled" |
32 | #videosSelection | 33 | #videosSelection |
diff --git a/client/src/app/+my-library/my-history/my-history.component.scss b/client/src/app/+my-library/my-history/my-history.component.scss index 3257b2215..21011a089 100644 --- a/client/src/app/+my-library/my-history/my-history.component.scss +++ b/client/src/app/+my-library/my-history/my-history.component.scss | |||
@@ -6,7 +6,6 @@ | |||
6 | justify-content: center; | 6 | justify-content: center; |
7 | margin-top: 50px; | 7 | margin-top: 50px; |
8 | font-weight: $font-semibold; | 8 | font-weight: $font-semibold; |
9 | font-size: 16px; | ||
10 | } | 9 | } |
11 | 10 | ||
12 | .top-buttons { | 11 | .top-buttons { |
@@ -29,11 +28,11 @@ | |||
29 | 28 | ||
30 | grid-column: 3; | 29 | grid-column: 3; |
31 | display: flex; | 30 | display: flex; |
31 | align-items: center; | ||
32 | 32 | ||
33 | label { | 33 | label { |
34 | margin: 0 0 0 5px; | 34 | margin: 0 0 0 5px; |
35 | color: var(--greyForegroundColor); | 35 | color: var(--greyForegroundColor); |
36 | font-size: 15px; | ||
37 | font-weight: $font-semibold; | 36 | font-weight: $font-semibold; |
38 | } | 37 | } |
39 | } | 38 | } |
@@ -44,8 +43,6 @@ | |||
44 | @include button-with-icon; | 43 | @include button-with-icon; |
45 | 44 | ||
46 | grid-column: 4; | 45 | grid-column: 4; |
47 | |||
48 | font-size: 15px; | ||
49 | } | 46 | } |
50 | } | 47 | } |
51 | 48 | ||
diff --git a/client/src/app/+my-library/my-history/my-history.component.ts b/client/src/app/+my-library/my-history/my-history.component.ts index f6b712908..c4878c957 100644 --- a/client/src/app/+my-library/my-history/my-history.component.ts +++ b/client/src/app/+my-library/my-history/my-history.component.ts | |||
@@ -93,8 +93,8 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook { | |||
93 | .subscribe({ | 93 | .subscribe({ |
94 | next: () => { | 94 | next: () => { |
95 | const message = this.videosHistoryEnabled === true | 95 | const message = this.videosHistoryEnabled === true |
96 | ? $localize`Videos history is enabled` | 96 | ? $localize`Video history is enabled` |
97 | : $localize`Videos history is disabled` | 97 | : $localize`Video history is disabled` |
98 | 98 | ||
99 | this.notifier.success(message) | 99 | this.notifier.success(message) |
100 | 100 | ||
@@ -117,8 +117,8 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook { | |||
117 | } | 117 | } |
118 | 118 | ||
119 | async clearAllHistory () { | 119 | async clearAllHistory () { |
120 | const title = $localize`Delete videos history` | 120 | const title = $localize`Delete video history` |
121 | const message = $localize`Are you sure you want to delete all your videos history?` | 121 | const message = $localize`Are you sure you want to delete all your video history?` |
122 | 122 | ||
123 | const res = await this.confirmService.confirm(message, title) | 123 | const res = await this.confirmService.confirm(message, title) |
124 | if (res !== true) return | 124 | if (res !== true) return |
@@ -126,7 +126,7 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook { | |||
126 | this.userHistoryService.clearAll() | 126 | this.userHistoryService.clearAll() |
127 | .subscribe({ | 127 | .subscribe({ |
128 | next: () => { | 128 | next: () => { |
129 | this.notifier.success($localize`Videos history deleted`) | 129 | this.notifier.success($localize`Video history deleted`) |
130 | 130 | ||
131 | this.reloadData() | 131 | this.reloadData() |
132 | }, | 132 | }, |
@@ -134,4 +134,12 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook { | |||
134 | error: err => this.notifier.error(err.message) | 134 | error: err => this.notifier.error(err.message) |
135 | }) | 135 | }) |
136 | } | 136 | } |
137 | |||
138 | getNoResultMessage () { | ||
139 | if (this.search) { | ||
140 | return $localize`No videos found for "${this.search}".` | ||
141 | } | ||
142 | |||
143 | return $localize`You don't have any video in your watch history yet.` | ||
144 | } | ||
137 | } | 145 | } |
diff --git a/client/src/app/+my-library/my-library.component.html b/client/src/app/+my-library/my-library.component.html index b465d0156..1c44c8472 100644 --- a/client/src/app/+my-library/my-library.component.html +++ b/client/src/app/+my-library/my-library.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div class="row"> | 1 | <div class="root"> |
2 | <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown> | 2 | <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown> |
3 | 3 | ||
4 | <div class="margin-content pb-5" [ngClass]="{ 'offset-content': !isBroadcastMessageDisplayed }"> | 4 | <div class="margin-content pb-5" [ngClass]="{ 'offset-content': !isBroadcastMessageDisplayed }"> |
diff --git a/client/src/app/+my-library/my-library.component.scss b/client/src/app/+my-library/my-library.component.scss index 1ec25315a..6275b7ac2 100644 --- a/client/src/app/+my-library/my-library.component.scss +++ b/client/src/app/+my-library/my-library.component.scss | |||
@@ -1,7 +1,7 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .row { | 4 | .root { |
5 | @include sub-menu-h1; | 5 | @include sub-menu-h1; |
6 | 6 | ||
7 | flex-direction: column; | 7 | flex-direction: column; |
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.html b/client/src/app/+my-library/my-ownership/my-ownership.component.html index c29c71c0a..649b3fef1 100644 --- a/client/src/app/+my-library/my-ownership/my-ownership.component.html +++ b/client/src/app/+my-library/my-ownership/my-ownership.component.html | |||
@@ -37,7 +37,7 @@ | |||
37 | <td> | 37 | <td> |
38 | <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> | 38 | <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> |
39 | <div class="chip two-lines"> | 39 | <div class="chip two-lines"> |
40 | <my-actor-avatar [account]="videoChangeOwnership.initiatorAccount" size="32"></my-actor-avatar> | 40 | <my-actor-avatar [actor]="videoChangeOwnership.initiatorAccount" actorType="account" size="32"></my-actor-avatar> |
41 | <div> | 41 | <div> |
42 | {{ videoChangeOwnership.initiatorAccount.displayName }} | 42 | {{ videoChangeOwnership.initiatorAccount.displayName }} |
43 | <span class="muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span> | 43 | <span class="muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span> |
@@ -65,7 +65,7 @@ | |||
65 | <td>{{ videoChangeOwnership.createdAt | date: 'short' }}</td> | 65 | <td>{{ videoChangeOwnership.createdAt | date: 'short' }}</td> |
66 | 66 | ||
67 | <td> | 67 | <td> |
68 | <span class="badge" | 68 | <span class="pt-badge" |
69 | [ngClass]="getStatusClass(videoChangeOwnership.status)">{{ videoChangeOwnership.status }}</span> | 69 | [ngClass]="getStatusClass(videoChangeOwnership.status)">{{ videoChangeOwnership.status }}</span> |
70 | </td> | 70 | </td> |
71 | </tr> | 71 | </tr> |
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.scss b/client/src/app/+my-library/my-ownership/my-ownership.component.scss index d32477270..a8450ff1b 100644 --- a/client/src/app/+my-library/my-ownership/my-ownership.component.scss +++ b/client/src/app/+my-library/my-ownership/my-ownership.component.scss | |||
@@ -55,13 +55,6 @@ | |||
55 | color: pvar(--mainForegroundColor); | 55 | color: pvar(--mainForegroundColor); |
56 | line-height: 1rem; | 56 | line-height: 1rem; |
57 | 57 | ||
58 | div .glyphicon { | ||
59 | @include margin-left(0.1rem); | ||
60 | |||
61 | font-size: 80%; | ||
62 | color: #808080; | ||
63 | } | ||
64 | |||
65 | div + div { | 58 | div + div { |
66 | font-size: 80%; | 59 | font-size: 80%; |
67 | } | 60 | } |
diff --git a/client/src/app/+my-library/my-video-imports/my-video-imports.component.html b/client/src/app/+my-library/my-video-imports/my-video-imports.component.html index 1525d0bd1..79fb4da26 100644 --- a/client/src/app/+my-library/my-video-imports/my-video-imports.component.html +++ b/client/src/app/+my-library/my-video-imports/my-video-imports.component.html | |||
@@ -50,7 +50,7 @@ | |||
50 | </td> | 50 | </td> |
51 | 51 | ||
52 | <td> | 52 | <td> |
53 | <span class="badge" [ngClass]="getVideoImportStateClass(videoImport.state.id)"> | 53 | <span class="pt-badge" [ngClass]="getVideoImportStateClass(videoImport.state.id)"> |
54 | {{ videoImport.state.label }} | 54 | {{ videoImport.state.label }} |
55 | </span> | 55 | </span> |
56 | </td> | 56 | </td> |
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.html b/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.html index c39e90a1e..e867f63b8 100644 --- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.html +++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.html | |||
@@ -20,13 +20,13 @@ | |||
20 | 20 | ||
21 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | 21 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> |
22 | 22 | ||
23 | <div class="form-row"> <!-- playlist grid --> | 23 | <div class="row"> <!-- playlist grid --> |
24 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 24 | <div class="col-12 col-lg-4 col-xl-3"> |
25 | <div *ngIf="isCreation()" class="video-playlist-title" i18n>NEW PLAYLIST</div> | 25 | <div *ngIf="isCreation()" class="video-playlist-title" i18n>NEW PLAYLIST</div> |
26 | <div *ngIf="!isCreation() && videoPlaylistToUpdate" class="video-playlist-title" i18n>PLAYLIST</div> | 26 | <div *ngIf="!isCreation() && videoPlaylistToUpdate" class="video-playlist-title" i18n>PLAYLIST</div> |
27 | </div> | 27 | </div> |
28 | 28 | ||
29 | <div class="form-group col-12 col-lg-8 col-xl-9"> | 29 | <div class="col-12 col-lg-8 col-xl-9"> |
30 | 30 | ||
31 | <div class="col-md-12 col-xl-6"> | 31 | <div class="col-md-12 col-xl-6"> |
32 | <div class="form-group"> | 32 | <div class="form-group"> |
@@ -42,13 +42,7 @@ | |||
42 | 42 | ||
43 | <div class="form-group"> | 43 | <div class="form-group"> |
44 | <label i18n for="description">Description</label><my-help helpType="markdownText"></my-help> | 44 | <label i18n for="description">Description</label><my-help helpType="markdownText"></my-help> |
45 | <my-markdown-textarea | 45 | <my-markdown-textarea id="description" formControlName="description" [formError]="formErrors['description']"></my-markdown-textarea> |
46 | id="description" formControlName="description" | ||
47 | [ngClass]="{ 'input-error': formErrors['description'] }" | ||
48 | ></my-markdown-textarea> | ||
49 | <div *ngIf="formErrors.description" class="form-error"> | ||
50 | {{ formErrors.description }} | ||
51 | </div> | ||
52 | </div> | 46 | </div> |
53 | </div> | 47 | </div> |
54 | 48 | ||
@@ -88,7 +82,7 @@ | |||
88 | </div> | 82 | </div> |
89 | </div> | 83 | </div> |
90 | 84 | ||
91 | <div class="form-row"> <!-- submit placement block --> | 85 | <div class="row"> <!-- submit placement block --> |
92 | <div class="col-md-7 col-xl-5"></div> | 86 | <div class="col-md-7 col-xl-5"></div> |
93 | <div class="col-md-5 col-xl-5 d-inline-flex"> | 87 | <div class="col-md-5 col-xl-5 d-inline-flex"> |
94 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> | 88 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> |
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.scss b/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.scss index 840e449ea..93bc18fe2 100644 --- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.scss +++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-edit.component.scss | |||
@@ -1,11 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | .video-playlist-title { | 4 | .video-playlist-title { |
10 | @include settings-big-title; | 5 | @include settings-big-title; |
11 | } | 6 | } |
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html index 25b742bff..0091f70be 100644 --- a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html +++ b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html | |||
@@ -1,6 +1,7 @@ | |||
1 | <h1> | 1 | <h1> |
2 | <my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon> | 2 | <my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon> |
3 | <ng-container i18n>My playlists</ng-container> <span class="badge badge-secondary">{{ pagination.totalItems }}</span> | 3 | <ng-container i18n>My playlists</ng-container> |
4 | <span *ngIf="pagination.totalItems" class="pt-badge badge-secondary">{{ pagination.totalItems }}</span> | ||
4 | </h1> | 5 | </h1> |
5 | 6 | ||
6 | <my-channels-setup-message></my-channels-setup-message> | 7 | <my-channels-setup-message></my-channels-setup-message> |
diff --git a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.html b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.html index 955fd4884..56ff0e788 100644 --- a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.html +++ b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.html | |||
@@ -17,7 +17,7 @@ | |||
17 | </div> | 17 | </div> |
18 | 18 | ||
19 | <div class="modal-footer"> | 19 | <div class="modal-footer"> |
20 | <div class="form-group inputs"> | 20 | <div class="inputs"> |
21 | <input | 21 | <input |
22 | type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button" | 22 | type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button" |
23 | (click)="dismiss()" (key.enter)="dismiss()" | 23 | (click)="dismiss()" (key.enter)="dismiss()" |
diff --git a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss index 0eb694162..48c073192 100644 --- a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss +++ b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss | |||
@@ -4,7 +4,3 @@ | |||
4 | p-autocomplete { | 4 | p-autocomplete { |
5 | display: block; | 5 | display: block; |
6 | } | 6 | } |
7 | |||
8 | .form-group { | ||
9 | margin: 20px 0; | ||
10 | } | ||
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.html b/client/src/app/+my-library/my-videos/my-videos.component.html index 7f12e2c71..146dcf41e 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.html +++ b/client/src/app/+my-library/my-videos/my-videos.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <span> | 2 | <span> |
3 | <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon> | 3 | <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon> |
4 | <ng-container i18n>My videos</ng-container> | 4 | <ng-container i18n>My videos</ng-container> |
5 | <span class="badge badge-secondary"> {{ pagination.totalItems }}</span> | 5 | <span *ngIf="pagination.totalItems" class="pt-badge badge-secondary"> {{ pagination.totalItems }}</span> |
6 | </span> | 6 | </span> |
7 | 7 | ||
8 | <div> | 8 | <div> |
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts index 91cc06702..2f1eb84ba 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.ts +++ b/client/src/app/+my-library/my-videos/my-videos.component.ts | |||
@@ -4,7 +4,7 @@ import { Component, OnInit, ViewChild } from '@angular/core' | |||
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' | 5 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' |
6 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 6 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
7 | import { immutableAssign } from '@app/helpers' | 7 | import { prepareIcu, immutableAssign } from '@app/helpers' |
8 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 8 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
10 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' | 10 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' |
@@ -167,7 +167,10 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
167 | .map(k => parseInt(k, 10)) | 167 | .map(k => parseInt(k, 10)) |
168 | 168 | ||
169 | const res = await this.confirmService.confirm( | 169 | const res = await this.confirmService.confirm( |
170 | $localize`Do you really want to delete ${toDeleteVideosIds.length} videos?`, | 170 | prepareIcu($localize`Do you really want to delete {length, plural, =1 {this video} other {{length} videos}}?`)( |
171 | { length: toDeleteVideosIds.length }, | ||
172 | $localize`Do you really want to delete ${toDeleteVideosIds.length} videos?` | ||
173 | ), | ||
171 | $localize`Delete` | 174 | $localize`Delete` |
172 | ) | 175 | ) |
173 | if (res === false) return | 176 | if (res === false) return |
@@ -184,7 +187,13 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
184 | .pipe(toArray()) | 187 | .pipe(toArray()) |
185 | .subscribe({ | 188 | .subscribe({ |
186 | next: () => { | 189 | next: () => { |
187 | this.notifier.success($localize`${toDeleteVideosIds.length} videos deleted.`) | 190 | this.notifier.success( |
191 | prepareIcu($localize`{length, plural, =1 {Video has been deleted} other {{length} videos have been deleted}}`)( | ||
192 | { length: toDeleteVideosIds.length }, | ||
193 | $localize`${toDeleteVideosIds.length} have been deleted.` | ||
194 | ) | ||
195 | ) | ||
196 | |||
188 | this.selection = {} | 197 | this.selection = {} |
189 | }, | 198 | }, |
190 | 199 | ||
diff --git a/client/src/app/+page-not-found/page-not-found.component.html b/client/src/app/+page-not-found/page-not-found.component.html index 0333f9550..70ede26e8 100644 --- a/client/src/app/+page-not-found/page-not-found.component.html +++ b/client/src/app/+page-not-found/page-not-found.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | <div *ngIf="status !== 403 && status !== 418" class="box"> | 2 | <div *ngIf="status !== 403 && status !== 418" class="box"> |
3 | <strong>{{ status }}.</strong> | 3 | <strong>{{ status }}.</strong> |
4 | <span class="ml-1 muted" i18n>That's an error.</span> | 4 | <span class="ms-1 muted" i18n>That's an error.</span> |
5 | 5 | ||
6 | <div class="text mt-4"> | 6 | <div class="text mt-4"> |
7 | <ng-container *ngIf="type === 'video'" i18n>We couldn't find any video tied to the URL {{ pathname }} you were looking for.</ng-container> | 7 | <ng-container *ngIf="type === 'video'" i18n>We couldn't find any video tied to the URL {{ pathname }} you were looking for.</ng-container> |
@@ -24,7 +24,7 @@ | |||
24 | 24 | ||
25 | <div *ngIf="status === 403" class="box"> | 25 | <div *ngIf="status === 403" class="box"> |
26 | <strong>{{ status }}.</strong> | 26 | <strong>{{ status }}.</strong> |
27 | <span class="ml-1 muted" i18n>You are not authorized here.</span> | 27 | <span class="ms-1 muted" i18n>You are not authorized here.</span> |
28 | 28 | ||
29 | <div class="text mt-4"> | 29 | <div class="text mt-4"> |
30 | <ng-container *ngIf="type === 'video'" i18n>You might need to check your account is allowed by the video or instance owner.</ng-container> | 30 | <ng-container *ngIf="type === 'video'" i18n>You might need to check your account is allowed by the video or instance owner.</ng-container> |
@@ -34,7 +34,7 @@ | |||
34 | 34 | ||
35 | <div *ngIf="status === 418" class="box"> | 35 | <div *ngIf="status === 418" class="box"> |
36 | <strong>{{ status }}.</strong> | 36 | <strong>{{ status }}.</strong> |
37 | <span class="ml-1 muted">I'm a teapot.</span> | 37 | <span class="ms-1 muted">I'm a teapot.</span> |
38 | 38 | ||
39 | <div class="text mt-4" i18n="Description of a tea flavour, keeping the 'requested entity body' as a technical expression referring to a web request"> | 39 | <div class="text mt-4" i18n="Description of a tea flavour, keeping the 'requested entity body' as a technical expression referring to a web request"> |
40 | The requested entity body blends sweet bits with a mellow earthiness. | 40 | The requested entity body blends sweet bits with a mellow earthiness. |
diff --git a/client/src/app/+remote-interaction/remote-interaction.component.html b/client/src/app/+remote-interaction/remote-interaction.component.html index e59783b9a..135ddc00b 100644 --- a/client/src/app/+remote-interaction/remote-interaction.component.html +++ b/client/src/app/+remote-interaction/remote-interaction.component.html | |||
@@ -1,7 +1,5 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | 2 | ||
3 | <div class="alert alert-error" *ngIf="error"> | 3 | <div class="alert alert-danger" *ngIf="error">{{ error }}</div> |
4 | {{ error }} | ||
5 | </div> | ||
6 | 4 | ||
7 | </div> | 5 | </div> |
diff --git a/client/src/app/+reset-password/reset-password.component.html b/client/src/app/+reset-password/reset-password.component.html index 143f426ec..fa53e7526 100644 --- a/client/src/app/+reset-password/reset-password.component.html +++ b/client/src/app/+reset-password/reset-password.component.html | |||
@@ -1,31 +1,28 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <div i18n class="title-page title-page-single"> | 2 | <h1 i18n class="title-page">Reset my password</h1> |
3 | Reset my password | ||
4 | </div> | ||
5 | 3 | ||
6 | <form role="form" (ngSubmit)="resetPassword()" [formGroup]="form"> | 4 | <form role="form" (ngSubmit)="resetPassword()" [formGroup]="form"> |
7 | <div class="form-group"> | 5 | <div class="form-group"> |
8 | <label i18n for="password">Password</label> | 6 | <label i18n for="password">Password</label> |
9 | <my-input-toggle-hidden formControlName="password" inputId="password" | 7 | |
10 | i18n-placeholder placeholder="Password" | 8 | <my-input-text |
11 | [ngClass]="{ 'input-error': formErrors['password'] }" | 9 | formControlName="password" inputId="password" i18n-placeholder placeholder="Password" |
12 | autocomplete="new-password"></my-input-toggle-hidden> | 10 | [formError]="formErrors['password']" autocomplete="new-password" |
13 | <div *ngIf="formErrors.password" class="form-error"> | 11 | ></my-input-text> |
14 | {{ formErrors.password }} | ||
15 | </div> | ||
16 | </div> | 12 | </div> |
17 | 13 | ||
18 | <div class="form-group"> | 14 | <div class="form-group"> |
19 | <label i18n for="password-confirm">Confirm password</label> | 15 | <label i18n for="password-confirm">Confirm password</label> |
20 | <my-input-toggle-hidden formControlName="password-confirm" inputId="password-confirm" | 16 | |
21 | i18n-placeholder placeholder="Confirmed password" | 17 | <my-input-text |
22 | [ngClass]="{ 'input-error': formErrors['password-confirm'] }" | 18 | formControlName="password-confirm" inputId="password-confirm" i18n-placeholder placeholder="Confirmed password" |
23 | autocomplete="new-password"></my-input-toggle-hidden> | 19 | [formError]="formErrors['password-confirm']" autocomplete="new-password" |
24 | <div *ngIf="formErrors['password-confirm']" class="form-error"> | 20 | ></my-input-text> |
25 | {{ formErrors['password-confirm'] }} | ||
26 | </div> | ||
27 | </div> | 21 | </div> |
28 | 22 | ||
29 | <input type="submit" i18n-value value="Reset my password" [disabled]="!form.valid || !isConfirmedPasswordValid()"> | 23 | <input |
24 | class="peertube-button orange-button" type="submit" i18n-value value="Reset my password" | ||
25 | [disabled]="!form.valid || !isConfirmedPasswordValid()" | ||
26 | > | ||
30 | </form> | 27 | </form> |
31 | </div> | 28 | </div> |
diff --git a/client/src/app/+reset-password/reset-password.component.scss b/client/src/app/+reset-password/reset-password.component.scss index 5494e29c6..dd981ca39 100644 --- a/client/src/app/+reset-password/reset-password.component.scss +++ b/client/src/app/+reset-password/reset-password.component.scss | |||
@@ -1,12 +1,7 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | input:not([type=submit]) { | 4 | my-input-text { |
5 | @include peertube-input-text(340px); | 5 | max-width: 340px; |
6 | display: block; | 6 | display: block; |
7 | } | 7 | } |
8 | |||
9 | input[type=submit] { | ||
10 | @include peertube-button; | ||
11 | @include orange-button; | ||
12 | } | ||
diff --git a/client/src/app/+search/search-filters.component.html b/client/src/app/+search/search-filters.component.html index c4861e8c4..5bce009d5 100644 --- a/client/src/app/+search/search-filters.component.html +++ b/client/src/app/+search/search-filters.component.html | |||
@@ -77,7 +77,7 @@ | |||
77 | </div> | 77 | </div> |
78 | 78 | ||
79 | <div class="row"> | 79 | <div class="row"> |
80 | <div class="pl-0 col-sm-6"> | 80 | <div class="ps-0 col-sm-6"> |
81 | <input | 81 | <input |
82 | (change)="onDurationOrPublishedUpdated()" | 82 | (change)="onDurationOrPublishedUpdated()" |
83 | (keydown.enter)="$event.preventDefault()" | 83 | (keydown.enter)="$event.preventDefault()" |
@@ -87,7 +87,7 @@ | |||
87 | class="form-control" | 87 | class="form-control" |
88 | > | 88 | > |
89 | </div> | 89 | </div> |
90 | <div class="pr-0 col-sm-6"> | 90 | <div class="pe-0 col-sm-6"> |
91 | <input | 91 | <input |
92 | (change)="onDurationOrPublishedUpdated()" | 92 | (change)="onDurationOrPublishedUpdated()" |
93 | (keydown.enter)="$event.preventDefault()" | 93 | (keydown.enter)="$event.preventDefault()" |
diff --git a/client/src/app/+search/search-filters.component.scss b/client/src/app/+search/search-filters.component.scss index ece4ba5b5..c55e59310 100644 --- a/client/src/app/+search/search-filters.component.scss +++ b/client/src/app/+search/search-filters.component.scss | |||
@@ -6,7 +6,6 @@ form { | |||
6 | } | 6 | } |
7 | 7 | ||
8 | .radio-label { | 8 | .radio-label { |
9 | font-size: 15px; | ||
10 | font-weight: $font-bold; | 9 | font-weight: $font-bold; |
11 | } | 10 | } |
12 | 11 | ||
@@ -22,10 +21,6 @@ form { | |||
22 | margin-bottom: 1rem; | 21 | margin-bottom: 1rem; |
23 | } | 22 | } |
24 | 23 | ||
25 | .form-group { | ||
26 | margin-bottom: 25px; | ||
27 | } | ||
28 | |||
29 | input[type=text] { | 24 | input[type=text] { |
30 | @include peertube-input-text(100%); | 25 | @include peertube-input-text(100%); |
31 | display: block; | 26 | display: block; |
diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html index 2c84dd930..37da67005 100644 --- a/client/src/app/+search/search.component.html +++ b/client/src/app/+search/search.component.html | |||
@@ -2,22 +2,22 @@ | |||
2 | <div class="results-header"> | 2 | <div class="results-header"> |
3 | <div class="first-line"> | 3 | <div class="first-line"> |
4 | <div class="results-counter" *ngIf="pagination.totalItems"> | 4 | <div class="results-counter" *ngIf="pagination.totalItems"> |
5 | <span class="mr-1" i18n>{{ pagination.totalItems | myNumberFormatter }} {pagination.totalItems, plural, =1 {result} other {results}}</span> | 5 | <span class="me-1" i18n>{{ pagination.totalItems | myNumberFormatter }} {pagination.totalItems, plural, =1 {result} other {results}}</span> |
6 | 6 | ||
7 | <span class="mr-1" i18n *ngIf="advancedSearch.searchTarget === 'local'">on this instance</span> | 7 | <span class="me-1" i18n *ngIf="advancedSearch.searchTarget === 'local'">on this instance</span> |
8 | <span class="mr-1" i18n *ngIf="advancedSearch.searchTarget === 'search-index'">on the vidiverse</span> | 8 | <span class="me-1" i18n *ngIf="advancedSearch.searchTarget === 'search-index'">on the vidiverse</span> |
9 | 9 | ||
10 | <span *ngIf="currentSearch" i18n>for <span class="search-value">{{ currentSearch }}</span></span> | 10 | <span *ngIf="currentSearch" i18n>for <span class="search-value">{{ currentSearch }}</span></span> |
11 | </div> | 11 | </div> |
12 | 12 | ||
13 | <div | 13 | <div |
14 | class="results-filter-button ml-auto" (click)="isSearchFilterCollapsed = !isSearchFilterCollapsed" role="button" | 14 | class="results-filter-button ms-auto" (click)="isSearchFilterCollapsed = !isSearchFilterCollapsed" role="button" |
15 | [attr.aria-expanded]="!isSearchFilterCollapsed" aria-controls="collapseBasic" | 15 | [attr.aria-expanded]="!isSearchFilterCollapsed" aria-controls="collapseBasic" |
16 | > | 16 | > |
17 | <span class="icon icon-filter"></span> | 17 | <span class="icon icon-filter"></span> |
18 | <ng-container i18n> | 18 | <ng-container i18n> |
19 | Filters | 19 | Filters |
20 | <span *ngIf="numberOfFilters() > 0" class="badge badge-secondary">{{ numberOfFilters() }}</span> | 20 | <span *ngIf="numberOfFilters() > 0" class="pt-badge badge-secondary">{{ numberOfFilters() }}</span> |
21 | </ng-container> | 21 | </ng-container> |
22 | </div> | 22 | </div> |
23 | </div> | 23 | </div> |
@@ -36,7 +36,7 @@ | |||
36 | <ng-container *ngFor="let result of results"> | 36 | <ng-container *ngFor="let result of results"> |
37 | <div *ngIf="isVideoChannel(result)" class="entry video-channel"> | 37 | <div *ngIf="isVideoChannel(result)" class="entry video-channel"> |
38 | 38 | ||
39 | <my-actor-avatar [channel]="result" [internalHref]="getInternalChannelUrl(result)" [href]="getExternalChannelUrl(result)" size="120"></my-actor-avatar> | 39 | <my-actor-avatar [actor]="result" actorType="channel" [internalHref]="getInternalChannelUrl(result)" [href]="getExternalChannelUrl(result)" size="120"></my-actor-avatar> |
40 | 40 | ||
41 | <div class="video-channel-info"> | 41 | <div class="video-channel-info"> |
42 | <a *ngIf="!isExternalChannelUrl()" [routerLink]="getInternalChannelUrl(result)" class="video-channel-names"> | 42 | <a *ngIf="!isExternalChannelUrl()" [routerLink]="getInternalChannelUrl(result)" class="video-channel-names"> |
diff --git a/client/src/app/+search/search.component.scss b/client/src/app/+search/search.component.scss index cab1d0e88..11d95df6a 100644 --- a/client/src/app/+search/search.component.scss +++ b/client/src/app/+search/search.component.scss | |||
@@ -20,7 +20,6 @@ | |||
20 | } | 20 | } |
21 | 21 | ||
22 | .results-header { | 22 | .results-header { |
23 | font-size: 16px; | ||
24 | padding-bottom: 20px; | 23 | padding-bottom: 20px; |
25 | margin-bottom: 30px; | 24 | margin-bottom: 30px; |
26 | border-bottom: 1px solid #DADADA; | 25 | border-bottom: 1px solid #DADADA; |
diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts index b9ec6dbcc..62b1c4446 100644 --- a/client/src/app/+search/search.component.ts +++ b/client/src/app/+search/search.component.ts | |||
@@ -248,11 +248,11 @@ export class SearchComponent implements OnInit, OnDestroy { | |||
248 | } | 248 | } |
249 | 249 | ||
250 | private updateTitle () { | 250 | private updateTitle () { |
251 | const suffix = this.currentSearch | 251 | const title = this.currentSearch |
252 | ? ' ' + this.currentSearch | 252 | ? $localize`Search ${this.currentSearch}` |
253 | : '' | 253 | : $localize`Search` |
254 | 254 | ||
255 | this.metaService.setTitle($localize`Search` + suffix) | 255 | this.metaService.setTitle(title) |
256 | } | 256 | } |
257 | 257 | ||
258 | private updateUrlFromAdvancedSearch () { | 258 | private updateUrlFromAdvancedSearch () { |
diff --git a/client/src/app/+signup/+register/custom-stepper.component.html b/client/src/app/+signup/+register/custom-stepper.component.html index aad2f31d3..f43a46842 100644 --- a/client/src/app/+signup/+register/custom-stepper.component.html +++ b/client/src/app/+signup/+register/custom-stepper.component.html | |||
@@ -1,24 +1,29 @@ | |||
1 | <section class="container"> | 1 | <section> |
2 | <header *ngIf="steps.length > 2"> | 2 | <header *ngIf="steps.length > 2"> |
3 | <ng-container *ngFor="let step of steps; let i = index; let isLast = last;"> | 3 | <div class="header-steps"> |
4 | <div | 4 | <ng-container *ngFor="let step of steps; let i = index; let isLast = last;"> |
5 | class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step), 'c-hand': isAccessible(i) }" [attr.aria-current]="selectedIndex === i" | 5 | <div |
6 | (click)="onClick(i)" | 6 | class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step), 'c-hand': isAccessible(step) }" [attr.aria-current]="selectedIndex === i" |
7 | > | 7 | (click)="onClick(i)" |
8 | <div class="step-index"> | 8 | > |
9 | <ng-container *ngIf="!isCompleted(step)"><span class="sr-only" i18n>Step</span> {{ i + 1 }}</ng-container> | 9 | <div class="step-index"> |
10 | <my-global-icon *ngIf="isCompleted(step)" iconName="tick"></my-global-icon> | 10 | <span class="visually-hidden" i18n>Step</span> {{ i + 1 }} |
11 | </div> | 11 | |
12 | <div class="completed-icon" *ngIf="isCompleted(step)"> | ||
13 | <my-global-icon iconName="tick"></my-global-icon> | ||
14 | </div> | ||
15 | </div> | ||
12 | 16 | ||
13 | <div class="step-label">{{ step.label }}</div> | 17 | <div class="step-label">{{ step.label }}</div> |
14 | </div> | 18 | </div> |
15 | 19 | ||
16 | <!-- Do no display if this is the last child --> | 20 | <!-- Do no display if this is the last child --> |
17 | <div *ngIf="!isLast" class="connector"></div> | 21 | <div *ngIf="!isLast" class="connector"></div> |
18 | </ng-container> | 22 | </ng-container> |
23 | </div> | ||
19 | </header> | 24 | </header> |
20 | 25 | ||
21 | <div [style.display]="selected ? 'block' : 'none'"> | 26 | <div class="margin-content" [style.display]="selected ? 'block' : 'none'"> |
22 | <ng-container [ngTemplateOutlet]="selected.content"></ng-container> | 27 | <ng-container [ngTemplateOutlet]="selected.content"></ng-container> |
23 | </div> | 28 | </div> |
24 | 29 | ||
diff --git a/client/src/app/+signup/+register/custom-stepper.component.scss b/client/src/app/+signup/+register/custom-stepper.component.scss index 6a8815c77..dfbdc863b 100644 --- a/client/src/app/+signup/+register/custom-stepper.component.scss +++ b/client/src/app/+signup/+register/custom-stepper.component.scss | |||
@@ -2,76 +2,113 @@ | |||
2 | @use '_variables' as *; | 2 | @use '_variables' as *; |
3 | @use '_mixins' as *; | 3 | @use '_mixins' as *; |
4 | 4 | ||
5 | $grey-color: #9CA3AB; | 5 | $index-block-height: 40px; |
6 | $index-block-height: 32px; | ||
7 | 6 | ||
8 | .container { | 7 | header { |
9 | @include padding-left(0); | 8 | margin-bottom: 40px; |
10 | @include padding-right(0); | 9 | padding-bottom: 60px; |
11 | max-width: unset !important; | 10 | width: 100%; |
11 | background-color: pvar(--mainColorVeryLight); | ||
12 | } | 12 | } |
13 | 13 | ||
14 | header { | 14 | .header-steps { |
15 | max-width: 800px; | ||
15 | display: flex; | 16 | display: flex; |
16 | justify-content: space-between; | 17 | justify-content: space-between; |
17 | font-size: 15px; | 18 | margin: auto; |
18 | margin-bottom: 30px; | 19 | |
20 | // Useful on small screens | ||
21 | padding: 0 20px; | ||
22 | } | ||
19 | 23 | ||
20 | .step-info { | 24 | .step-index { |
21 | color: $grey-color; | 25 | display: flex; |
26 | justify-content: center; | ||
27 | align-items: center; | ||
28 | width: $index-block-height; | ||
29 | height: $index-block-height; | ||
30 | border-radius: $index-block-height; | ||
31 | border: 1px solid pvar(--mainColor); | ||
32 | margin-bottom: 10px; | ||
33 | font-size: 24px; | ||
34 | position: relative; | ||
35 | |||
36 | .completed-icon { | ||
37 | width: 16px; | ||
38 | height: 16px; | ||
39 | border-radius: 16px; | ||
40 | background-color: pvar(--mainBackgroundColor); | ||
41 | position: absolute; | ||
42 | bottom: 0; | ||
43 | right: 0; | ||
22 | display: flex; | 44 | display: flex; |
23 | flex-direction: column; | 45 | justify-content: center; |
24 | align-items: center; | 46 | align-items: center; |
25 | width: $index-block-height; | 47 | border: 1px solid pvar(--mainColor); |
48 | |||
49 | my-global-icon { | ||
50 | @include apply-svg-color(pvar(--mainColor)); | ||
26 | 51 | ||
27 | &:not(.c-hand) { | 52 | display: flex; |
28 | cursor: default; | 53 | width: 12px; |
54 | height: 12px; | ||
29 | } | 55 | } |
56 | } | ||
57 | } | ||
30 | 58 | ||
59 | .step-label { | ||
60 | width: max-content; | ||
61 | font-size: 18px; | ||
62 | } | ||
63 | |||
64 | .step-info { | ||
65 | color: pvar(--mainColor); | ||
66 | display: flex; | ||
67 | flex-direction: column; | ||
68 | align-items: center; | ||
69 | width: $index-block-height; | ||
70 | opacity: 0.5; | ||
71 | cursor: default; | ||
72 | |||
73 | &.c-hand { | ||
74 | cursor: pointer; | ||
75 | } | ||
76 | |||
77 | &.active, | ||
78 | &.completed { | ||
31 | .step-index { | 79 | .step-index { |
32 | display: flex; | 80 | background-color: pvar(--mainColor); |
33 | justify-content: center; | 81 | color: pvar(--mainBackgroundColor); |
34 | align-items: center; | ||
35 | width: $index-block-height; | ||
36 | height: $index-block-height; | ||
37 | border-radius: 100px; | ||
38 | border: 2px solid $grey-color; | ||
39 | margin-bottom: 10px; | ||
40 | |||
41 | my-global-icon { | ||
42 | @include apply-svg-color(pvar(--mainBackgroundColor)); | ||
43 | |||
44 | width: 22px; | ||
45 | height: 22px; | ||
46 | } | ||
47 | } | 82 | } |
48 | 83 | ||
49 | .step-label { | 84 | .step-label { |
50 | width: max-content; | 85 | color: pvar(--mainColor); |
51 | } | 86 | } |
87 | } | ||
52 | 88 | ||
53 | &.active, | 89 | &.active { |
54 | &.completed { | 90 | opacity: 1; |
55 | .step-index { | 91 | } |
56 | border-color: pvar(--mainColor); | 92 | } |
57 | background-color: pvar(--mainColor); | ||
58 | color: pvar(--mainBackgroundColor); | ||
59 | } | ||
60 | |||
61 | .step-label { | ||
62 | color: pvar(--mainColor); | ||
63 | } | ||
64 | } | ||
65 | 93 | ||
66 | &.completed { | 94 | .connector { |
67 | cursor: pointer; | 95 | flex: auto; |
68 | } | 96 | margin: math.div($index-block-height, 2) 10px 0 10px; |
97 | height: 2px; | ||
98 | background-color: pvar(--mainColor); | ||
99 | opacity: 0.3; | ||
100 | } | ||
101 | |||
102 | @media screen and (min-width: $small-view) { | ||
103 | .margin-content { | ||
104 | max-width: 1000px; | ||
105 | margin: auto; | ||
69 | } | 106 | } |
107 | } | ||
70 | 108 | ||
71 | .connector { | 109 | @media screen and (max-width: $small-view) { |
72 | flex: auto; | 110 | .step-label { |
73 | margin: math.div($index-block-height, 2) 10px 0 10px; | 111 | width: auto; |
74 | height: 2px; | 112 | text-align: center; |
75 | background-color: $grey-color; | ||
76 | } | 113 | } |
77 | } | 114 | } |
diff --git a/client/src/app/+signup/+register/custom-stepper.component.ts b/client/src/app/+signup/+register/custom-stepper.component.ts index 3b7ba40e8..4c308f7b6 100644 --- a/client/src/app/+signup/+register/custom-stepper.component.ts +++ b/client/src/app/+signup/+register/custom-stepper.component.ts | |||
@@ -14,13 +14,10 @@ export class CustomStepperComponent extends CdkStepper { | |||
14 | } | 14 | } |
15 | 15 | ||
16 | isCompleted (step: CdkStep) { | 16 | isCompleted (step: CdkStep) { |
17 | return step.stepControl?.dirty && step.stepControl.valid | 17 | return step.completed |
18 | } | 18 | } |
19 | 19 | ||
20 | isAccessible (index: number) { | 20 | isAccessible (step: CdkStep) { |
21 | const stepsCompletedMap = this.steps.map(step => this.isCompleted(step)) | 21 | return step.editable && step.completed |
22 | return index === 0 | ||
23 | ? true | ||
24 | : stepsCompletedMap[index - 1] | ||
25 | } | 22 | } |
26 | } | 23 | } |
diff --git a/client/src/app/+signup/+register/register-step-channel.component.html b/client/src/app/+signup/+register/register-step-channel.component.html deleted file mode 100644 index 67f332409..000000000 --- a/client/src/app/+signup/+register/register-step-channel.component.html +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | <form role="form" [formGroup]="form"> | ||
2 | |||
3 | <div class="channel-explanations"> | ||
4 | <p i18n> | ||
5 | A channel is an entity in which you upload your videos. Creating several of them helps you to organize and separate your content.<br /> | ||
6 | For example, you could decide to have a channel to publish your piano concerts, and another channel in which you publish your videos talking about ecology. | ||
7 | </p> | ||
8 | |||
9 | <p i18n> | ||
10 | Other users can decide to subscribe any channel they want, to be notified when you publish a new video. | ||
11 | </p> | ||
12 | </div> | ||
13 | |||
14 | <div class="form-group"> | ||
15 | <label for="displayName" i18n>Channel display name</label> | ||
16 | |||
17 | <div class="input-group"> | ||
18 | <input | ||
19 | type="text" id="displayName" | ||
20 | formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }" | ||
21 | > | ||
22 | </div> | ||
23 | |||
24 | <div *ngIf="formErrors.displayName" class="form-error"> | ||
25 | {{ formErrors.displayName }} | ||
26 | </div> | ||
27 | </div> | ||
28 | |||
29 | <div class="form-group"> | ||
30 | <label for="name" i18n>Channel name</label> | ||
31 | |||
32 | <div class="input-group"> | ||
33 | <input | ||
34 | type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel" | ||
35 | formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" | ||
36 | > | ||
37 | <div class="input-group-append"> | ||
38 | <span class="input-group-text">@{{ instanceHost }}</span> | ||
39 | </div> | ||
40 | </div> | ||
41 | |||
42 | <div class="name-information" i18n> | ||
43 | The channel name is a unique identifier of your channel on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it. | ||
44 | </div> | ||
45 | |||
46 | <div *ngIf="formErrors.name" class="form-error"> | ||
47 | {{ formErrors.name }} | ||
48 | </div> | ||
49 | |||
50 | <div *ngIf="isSameThanUsername()" class="form-error" i18n> | ||
51 | Channel name cannot be the same as your account name. You can click on the first step to update your account name. | ||
52 | </div> | ||
53 | </div> | ||
54 | </form> | ||
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html deleted file mode 100644 index cab21c655..000000000 --- a/client/src/app/+signup/+register/register-step-user.component.html +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | <form role="form" [formGroup]="form"> | ||
2 | |||
3 | <div class="capability-information alert alert-info" i18n *ngIf="videoUploadDisabled"> | ||
4 | Video uploads are disabled on this instance, hence your account won't be able to upload videos. | ||
5 | </div> | ||
6 | |||
7 | <div class="form-group"> | ||
8 | <label for="displayName" i18n>Display name</label> | ||
9 | |||
10 | <div class="input-group"> | ||
11 | <input | ||
12 | type="text" id="displayName" placeholder="John Doe" | ||
13 | formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }" | ||
14 | > | ||
15 | </div> | ||
16 | |||
17 | <div *ngIf="formErrors.displayName" class="form-error"> | ||
18 | {{ formErrors.displayName }} | ||
19 | </div> | ||
20 | </div> | ||
21 | |||
22 | <div class="form-group"> | ||
23 | <label for="username" i18n>Username</label> | ||
24 | |||
25 | <div class="input-group"> | ||
26 | <input | ||
27 | type="text" id="username" i18n-placeholder="Username choice placeholder in the registration form" placeholder="e.g. jane_doe" | ||
28 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" | ||
29 | > | ||
30 | <div class="input-group-append"> | ||
31 | <span class="input-group-text">@{{ instanceHost }}</span> | ||
32 | </div> | ||
33 | </div> | ||
34 | |||
35 | <div class="name-information" i18n> | ||
36 | The username is a unique identifier of your account on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it. | ||
37 | </div> | ||
38 | |||
39 | <div *ngIf="formErrors.username" class="form-error"> | ||
40 | {{ formErrors.username }} | ||
41 | </div> | ||
42 | </div> | ||
43 | |||
44 | <div class="form-group"> | ||
45 | <label for="email" i18n>Email</label> | ||
46 | <input | ||
47 | type="text" id="email" i18n-placeholder placeholder="Email" | ||
48 | formControlName="email" class="form-control" [ngClass]="{ 'input-error': formErrors['email'] }" | ||
49 | > | ||
50 | <div *ngIf="formErrors.email" class="form-error"> | ||
51 | {{ formErrors.email }} | ||
52 | </div> | ||
53 | </div> | ||
54 | |||
55 | <div class="form-group"> | ||
56 | <label for="password" i18n>Password</label> | ||
57 | <my-input-toggle-hidden formControlName="password" inputId="password" | ||
58 | i18n-placeholder placeholder="Password" | ||
59 | [ngClass]="{ 'input-error': formErrors['password'] }" | ||
60 | autocomplete="new-password"></my-input-toggle-hidden> | ||
61 | <div *ngIf="formErrors.password" class="form-error"> | ||
62 | {{ formErrors.password }} | ||
63 | </div> | ||
64 | </div> | ||
65 | |||
66 | </form> | ||
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html index 2d0e6e865..442c65e2d 100644 --- a/client/src/app/+signup/+register/register.component.html +++ b/client/src/app/+signup/+register/register.component.html | |||
@@ -1,64 +1,121 @@ | |||
1 | <div class="margin-content"> | 1 | <div> |
2 | 2 | ||
3 | <div class="signup-disabled" *ngIf="signupDisabled"> | 3 | <div class="margin-content signup-disabled" *ngIf="signupDisabled"> |
4 | <div class="alert alert-warning" i18n>Signup is not enabled on this instance.</div> | 4 | <div class="alert alert-warning" i18n>Signup is not enabled on this instance.</div> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <ng-container *ngIf="!signupDisabled"> | 7 | <ng-container *ngIf="!signupDisabled"> |
8 | <div i18n class="title-page title-page-single"> | 8 | <h1 i18n class="title-page-v2"> |
9 | <strong class="underline-orange">{{ instanceName }}</strong> | ||
10 | > | ||
9 | Create an account | 11 | Create an account |
10 | </div> | 12 | </h1> |
11 | 13 | ||
12 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> | 14 | <div class="register-content"> |
13 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> | 15 | <my-custom-stepper linear> |
14 | 16 | ||
15 | <div class="wrapper" [hidden]="signupDone"> | 17 | <cdk-step i18n-label label="About" [editable]="!signupSuccess"> |
16 | <div class="register-form"> | 18 | <my-signup-step-title mascotImageName="about" i18n> |
17 | <my-custom-stepper linear *ngIf="!signupDone"> | 19 | <strong>Create an account</strong> |
18 | <cdk-step [stepControl]="formStepTerms" i18n-label="Stepper label for the registration page describing terms of service" label="Terms"> | 20 | <div>on {{ instanceName }}</div> |
19 | <div class="instance-information"> | 21 | </my-signup-step-title> |
20 | <my-instance-about-accordion | 22 | |
21 | (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels" | 23 | <my-register-step-about [videoUploadDisabled]="videoUploadDisabled"></my-register-step-about> |
22 | pluginScope="signup" pluginHook="filter:signup.instance-about-plugin-panels.create.result" | 24 | |
23 | ></my-instance-about-accordion> | 25 | <div class="step-buttons"> |
24 | </div> | 26 | <a i18n class="skip-step underline-orange" routerLink="/login"> |
27 | <strong>I already have an account</strong>, I log in | ||
28 | </a> | ||
25 | 29 | ||
26 | <my-register-step-terms | 30 | <button i18n cdkStepperNext>I create an account</button> |
27 | [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" | 31 | </div> |
28 | [minimumAge]="minimumAge" | 32 | </cdk-step> |
29 | (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" | ||
30 | ></my-register-step-terms> | ||
31 | 33 | ||
34 | <cdk-step [stepControl]="formStepTerms" i18n-label label="Terms" [editable]="!signupSuccess"> | ||
35 | <my-signup-step-title mascotImageName="terms" i18n> | ||
36 | <strong>Terms</strong> | ||
37 | <div>of {{ instanceName }}</div> | ||
38 | </my-signup-step-title> | ||
39 | |||
40 | <my-instance-about-accordion | ||
41 | [displayInstanceName]="false" | ||
42 | (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels" | ||
43 | pluginScope="signup" pluginHook="filter:signup.instance-about-plugin-panels.create.result" | ||
44 | ></my-instance-about-accordion> | ||
45 | |||
46 | <my-register-step-terms | ||
47 | [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" | ||
48 | [minimumAge]="minimumAge" | ||
49 | (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" | ||
50 | ></my-register-step-terms> | ||
51 | |||
52 | <div class="step-buttons"> | ||
53 | <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> | ||
32 | <button cdkStepperNext [disabled]="!formStepTerms || !formStepTerms.valid">{{ defaultNextStepButtonLabel }}</button> | 54 | <button cdkStepperNext [disabled]="!formStepTerms || !formStepTerms.valid">{{ defaultNextStepButtonLabel }}</button> |
33 | </cdk-step> | 55 | </div> |
56 | </cdk-step> | ||
57 | |||
58 | <cdk-step [stepControl]="formStepUser" label="My account" [editable]="!signupSuccess"> | ||
59 | <my-signup-step-title mascotImageName="account" i18n> | ||
60 | <strong>Setup</strong> | ||
61 | <div>your account</div> | ||
62 | </my-signup-step-title> | ||
34 | 63 | ||
35 | <cdk-step [stepControl]="formStepUser" i18n-label="Stepper label for the registration page asking user informations" label="User"> | 64 | <my-register-step-user |
36 | <my-register-step-user (formBuilt)="onUserFormBuilt($event)" [videoUploadDisabled]="videoUploadDisabled"></my-register-step-user> | 65 | (formBuilt)="onUserFormBuilt($event)" |
66 | [videoUploadDisabled]="videoUploadDisabled" [requiresEmailVerification]="requiresEmailVerification" | ||
67 | ></my-register-step-user> | ||
37 | 68 | ||
69 | <div class="step-buttons"> | ||
38 | <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> | 70 | <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> |
39 | <button cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" (click)="videoUploadDisabled && signup()">{{ stepUserButtonLabel }}</button> | 71 | <button cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" (click)="videoUploadDisabled && signup()">{{ stepUserButtonLabel }}</button> |
40 | </cdk-step> | 72 | </div> |
73 | </cdk-step> | ||
41 | 74 | ||
42 | <cdk-step [stepControl]="formStepChannel" i18n-label="Stepper label for the registration page asking information about the default channel" label="Channel" *ngIf="!videoUploadDisabled"> | 75 | <cdk-step *ngIf="!videoUploadDisabled" [optional]="true" [stepControl]="formStepChannel" i18n-label label="My channel" [editable]="!signupSuccess"> |
43 | <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel> | 76 | <my-signup-step-title mascotImageName="channel" i18n> |
77 | <div>Create</div> | ||
78 | <strong>your first channel</strong> | ||
79 | </my-signup-step-title> | ||
44 | 80 | ||
81 | <my-register-step-channel | ||
82 | (formBuilt)="onChannelFormBuilt($event)" | ||
83 | [videoQuota]="videoQuota" [instanceName]="instanceName" [username]="getUsername()" | ||
84 | ></my-register-step-channel> | ||
85 | |||
86 | <div class="step-buttons"> | ||
45 | <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> | 87 | <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> |
88 | |||
89 | <div class="skip-step"> | ||
90 | <span class="underline-orange" role="button" (click)="skipChannelCreation()"> | ||
91 | <strong i18n>I don't want to create a channel</strong> | ||
92 | </span> | ||
93 | |||
94 | <div class="skip-step-description" i18n>You will be able to create a channel later</div> | ||
95 | </div> | ||
96 | |||
46 | <button cdkStepperNext [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" (click)="signup()" i18n> | 97 | <button cdkStepperNext [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" (click)="signup()" i18n> |
47 | Create my account | 98 | Create my account |
48 | </button> | 99 | </button> |
49 | </cdk-step> | 100 | </div> |
101 | </cdk-step> | ||
50 | 102 | ||
51 | <cdk-step i18n-label label="Done" editable="false"> | 103 | <cdk-step #lastStep i18n-label label="Done!" [editable]="false"> |
52 | <div *ngIf="!signupDone && !error" class="done-loader"> | 104 | <div *ngIf="!signupSuccess && !signupError" class="done-loader"> |
53 | <my-loader [loading]="true"></my-loader> | 105 | <my-loader [loading]="true"></my-loader> |
54 | 106 | ||
55 | <div i18n>PeerTube is creating your account...</div> | 107 | <div i18n>PeerTube is creating your account...</div> |
56 | </div> | 108 | </div> |
109 | |||
110 | <div *ngIf="signupError" class="alert alert-danger">{{ signupError }}</div> | ||
57 | 111 | ||
58 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 112 | <my-signup-success *ngIf="signupSuccess" [requiresEmailVerification]="requiresEmailVerification"></my-signup-success> |
59 | </cdk-step> | 113 | |
60 | </my-custom-stepper> | 114 | <div *ngIf="signupError" class="steps-button"> |
61 | </div> | 115 | <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> |
116 | </div> | ||
117 | </cdk-step> | ||
118 | </my-custom-stepper> | ||
62 | </div> | 119 | </div> |
63 | </ng-container> | 120 | </ng-container> |
64 | 121 | ||
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss index 4be67a858..c706d6955 100644 --- a/client/src/app/+signup/+register/register.component.scss +++ b/client/src/app/+signup/+register/register.component.scss | |||
@@ -1,86 +1,72 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .alert { | ||
5 | font-size: 15px; | ||
6 | text-align: center; | ||
7 | } | ||
8 | |||
9 | .signup-disabled { | 4 | .signup-disabled { |
10 | padding-top: 30vh; | 5 | padding-top: 30vh; |
11 | } | 6 | } |
12 | 7 | ||
13 | .wrapper { | 8 | .title-page-v2 { |
9 | background-color: pvar(--mainColorVeryLight); | ||
10 | margin: 0; | ||
11 | } | ||
12 | |||
13 | my-instance-about-accordion { | ||
14 | display: block; | ||
15 | margin-bottom: 25px; | ||
16 | } | ||
17 | |||
18 | .step-buttons { | ||
14 | display: flex; | 19 | display: flex; |
15 | flex-direction: column; | 20 | flex-wrap: wrap; |
21 | align-items: center; | ||
16 | 22 | ||
17 | .register-form { | 23 | .skip-step { |
18 | max-width: 600px; | 24 | @include margin-right(30px); |
19 | align-self: center; | ||
20 | } | ||
21 | 25 | ||
22 | .register-form, | 26 | display: inline-block; |
23 | .instance-information { | ||
24 | width: 100%; | ||
25 | } | 27 | } |
26 | 28 | ||
27 | .instance-information { | 29 | .skip-step-description { |
28 | margin-bottom: 15px; | 30 | margin-top: 5px; |
31 | font-size: 14px; | ||
29 | } | 32 | } |
30 | } | ||
31 | 33 | ||
32 | .form-group-terms { | 34 | .underline-orange { |
33 | margin: 30px 0; | 35 | color: pvar(--mainForegroundColor); |
34 | } | ||
35 | 36 | ||
36 | .input-group { | 37 | &:hover { |
37 | @include peertube-input-group(100%); | 38 | opacity: 0.8; |
38 | } | 39 | } |
39 | 40 | } | |
40 | .input-group-append { | ||
41 | height: 30px; | ||
42 | } | ||
43 | 41 | ||
44 | .form-group-terms { | 42 | button, |
45 | width: 100% !important; | 43 | .skip-step { |
46 | } | 44 | margin-top: 20px; |
45 | margin-bottom: 20px; | ||
46 | } | ||
47 | 47 | ||
48 | input:not([type=submit]) { | 48 | .skip-step, |
49 | @include peertube-input-text(100%); | 49 | button[cdkStepperNext] { |
50 | display: block; | 50 | @include margin-left(auto); |
51 | } | ||
51 | 52 | ||
52 | &#username, | 53 | .skip-step + button[cdkStepperNext] { |
53 | &#name { | 54 | @include margin-left(0); |
54 | width: auto !important; | ||
55 | flex-grow: 1; | ||
56 | } | 55 | } |
57 | } | 56 | } |
58 | 57 | ||
59 | input[type=submit], | ||
60 | button { | 58 | button { |
61 | @include peertube-button; | 59 | @include peertube-button-big; |
62 | 60 | ||
63 | &[cdkStepperNext] { | 61 | &[cdkStepperNext] { |
64 | @include orange-button; | 62 | @include orange-button; |
65 | |||
66 | // Chrome does not support inline-end | ||
67 | float: right; | ||
68 | float: inline-end; | ||
69 | } | 63 | } |
70 | 64 | ||
71 | &[cdkStepperPrevious] { | 65 | &[cdkStepperPrevious] { |
72 | @include grey-button; | 66 | @include grey-button; |
73 | |||
74 | // Chrome does not support inline-start | ||
75 | float: left; | ||
76 | float: inline-start; | ||
77 | } | 67 | } |
78 | } | 68 | } |
79 | 69 | ||
80 | .name-information { | ||
81 | margin-top: 10px; | ||
82 | } | ||
83 | |||
84 | .done-loader { | 70 | .done-loader { |
85 | display: flex; | 71 | display: flex; |
86 | justify-content: center; | 72 | justify-content: center; |
@@ -89,13 +75,16 @@ button { | |||
89 | 75 | ||
90 | my-loader { | 76 | my-loader { |
91 | margin-bottom: 20px; | 77 | margin-bottom: 20px; |
78 | } | ||
79 | } | ||
92 | 80 | ||
93 | ::ng-deep .loader div { | 81 | @media screen and (max-width: $small-view) { |
94 | border-color: pvar(--mainColor) transparent transparent transparent; | 82 | .step-buttons { |
95 | } | 83 | justify-content: space-between; |
96 | 84 | ||
97 | + div { | 85 | .skip-step, |
98 | font-size: 15px; | 86 | button[cdkStepperNext] { |
87 | @include margin-left(0); | ||
99 | } | 88 | } |
100 | } | 89 | } |
101 | } | 90 | } |
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts index b4a7c0d0e..396b27e5a 100644 --- a/client/src/app/+signup/+register/register.component.ts +++ b/client/src/app/+signup/+register/register.component.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { CdkStep } from '@angular/cdk/stepper' |
2 | import { Component, OnInit, ViewChild } from '@angular/core' | ||
2 | import { FormGroup } from '@angular/forms' | 3 | import { FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute } from '@angular/router' | 4 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService } from '@app/core' | 5 | import { AuthService } from '@app/core' |
@@ -15,13 +16,15 @@ import { ServerConfig } from '@shared/models/server' | |||
15 | styleUrls: [ './register.component.scss' ] | 16 | styleUrls: [ './register.component.scss' ] |
16 | }) | 17 | }) |
17 | export class RegisterComponent implements OnInit { | 18 | export class RegisterComponent implements OnInit { |
19 | @ViewChild('lastStep') lastStep: CdkStep | ||
20 | |||
18 | accordion: NgbAccordion | 21 | accordion: NgbAccordion |
19 | info: string = null | 22 | |
20 | error: string = null | 23 | signupError: string |
21 | success: string = null | 24 | signupSuccess = false |
22 | signupDone = false | ||
23 | 25 | ||
24 | videoUploadDisabled: boolean | 26 | videoUploadDisabled: boolean |
27 | videoQuota: number | ||
25 | 28 | ||
26 | formStepTerms: FormGroup | 29 | formStepTerms: FormGroup |
27 | formStepUser: FormGroup | 30 | formStepUser: FormGroup |
@@ -39,8 +42,8 @@ export class RegisterComponent implements OnInit { | |||
39 | moderation: false | 42 | moderation: false |
40 | } | 43 | } |
41 | 44 | ||
42 | defaultPreviousStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Back` | 45 | defaultPreviousStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Go to the previous step` |
43 | defaultNextStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Next` | 46 | defaultNextStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Go to the next step` |
44 | stepUserButtonLabel = this.defaultNextStepButtonLabel | 47 | stepUserButtonLabel = this.defaultNextStepButtonLabel |
45 | 48 | ||
46 | signupDisabled = false | 49 | signupDisabled = false |
@@ -62,7 +65,11 @@ export class RegisterComponent implements OnInit { | |||
62 | return this.serverConfig.signup.minimumAge | 65 | return this.serverConfig.signup.minimumAge |
63 | } | 66 | } |
64 | 67 | ||
65 | ngOnInit (): void { | 68 | get instanceName () { |
69 | return this.serverConfig.instance.name | ||
70 | } | ||
71 | |||
72 | ngOnInit () { | ||
66 | this.serverConfig = this.route.snapshot.data.serverConfig | 73 | this.serverConfig = this.route.snapshot.data.serverConfig |
67 | 74 | ||
68 | if (this.serverConfig.signup.allowed === false || this.serverConfig.signup.allowedForCurrentIP === false) { | 75 | if (this.serverConfig.signup.allowed === false || this.serverConfig.signup.allowedForCurrentIP === false) { |
@@ -70,7 +77,9 @@ export class RegisterComponent implements OnInit { | |||
70 | return | 77 | return |
71 | } | 78 | } |
72 | 79 | ||
73 | this.videoUploadDisabled = this.serverConfig.user.videoQuota === 0 | 80 | this.videoQuota = this.serverConfig.user.videoQuota |
81 | this.videoUploadDisabled = this.videoQuota === 0 | ||
82 | |||
74 | this.stepUserButtonLabel = this.videoUploadDisabled | 83 | this.stepUserButtonLabel = this.videoUploadDisabled |
75 | ? $localize`:Button on the registration form to finalize the account and channel creation:Signup` | 84 | ? $localize`:Button on the registration form to finalize the account and channel creation:Signup` |
76 | : this.defaultNextStepButtonLabel | 85 | : this.defaultNextStepButtonLabel |
@@ -120,21 +129,31 @@ export class RegisterComponent implements OnInit { | |||
120 | this.aboutHtml = instanceAboutAccordion.aboutHtml | 129 | this.aboutHtml = instanceAboutAccordion.aboutHtml |
121 | } | 130 | } |
122 | 131 | ||
132 | skipChannelCreation () { | ||
133 | this.formStepChannel.reset() | ||
134 | this.lastStep.select() | ||
135 | this.signup() | ||
136 | } | ||
137 | |||
123 | async signup () { | 138 | async signup () { |
124 | this.error = null | 139 | this.signupError = undefined |
125 | 140 | ||
126 | const body: UserRegister = await this.hooks.wrapObject( | 141 | const body: UserRegister = await this.hooks.wrapObject( |
127 | Object.assign(this.formStepUser.value, { channel: this.videoUploadDisabled ? undefined : this.formStepChannel.value }), | 142 | { |
143 | ...this.formStepUser.value, | ||
144 | |||
145 | channel: this.formStepChannel?.value?.name | ||
146 | ? this.formStepChannel.value | ||
147 | : undefined | ||
148 | }, | ||
128 | 'signup', | 149 | 'signup', |
129 | 'filter:api.signup.registration.create.params' | 150 | 'filter:api.signup.registration.create.params' |
130 | ) | 151 | ) |
131 | 152 | ||
132 | this.userSignupService.signup(body).subscribe({ | 153 | this.userSignupService.signup(body).subscribe({ |
133 | next: () => { | 154 | next: () => { |
134 | this.signupDone = true | ||
135 | |||
136 | if (this.requiresEmailVerification) { | 155 | if (this.requiresEmailVerification) { |
137 | this.info = $localize`Now please check your emails to verify your account and complete signup.` | 156 | this.signupSuccess = true |
138 | return | 157 | return |
139 | } | 158 | } |
140 | 159 | ||
@@ -142,17 +161,17 @@ export class RegisterComponent implements OnInit { | |||
142 | this.authService.login(body.username, body.password) | 161 | this.authService.login(body.username, body.password) |
143 | .subscribe({ | 162 | .subscribe({ |
144 | next: () => { | 163 | next: () => { |
145 | this.success = $localize`You are now logged in as ${body.username}!` | 164 | this.signupSuccess = true |
146 | }, | 165 | }, |
147 | 166 | ||
148 | error: err => { | 167 | error: err => { |
149 | this.error = err.message | 168 | this.signupError = err.message |
150 | } | 169 | } |
151 | }) | 170 | }) |
152 | }, | 171 | }, |
153 | 172 | ||
154 | error: err => { | 173 | error: err => { |
155 | this.error = err.message | 174 | this.signupError = err.message |
156 | } | 175 | } |
157 | }) | 176 | }) |
158 | } | 177 | } |
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts index 52cdb33bc..684aae2e9 100644 --- a/client/src/app/+signup/+register/register.module.ts +++ b/client/src/app/+signup/+register/register.module.ts | |||
@@ -2,15 +2,15 @@ import { CdkStepperModule } from '@angular/cdk/stepper' | |||
2 | import { NgModule } from '@angular/core' | 2 | import { NgModule } from '@angular/core' |
3 | import { SharedSignupModule } from '@app/+signup/shared/shared-signup.module' | 3 | import { SharedSignupModule } from '@app/+signup/shared/shared-signup.module' |
4 | import { SharedInstanceModule } from '@app/shared/shared-instance' | 4 | import { SharedInstanceModule } from '@app/shared/shared-instance' |
5 | import { SharedMainModule } from '@app/shared/shared-main' | ||
5 | import { CustomStepperComponent } from './custom-stepper.component' | 6 | import { CustomStepperComponent } from './custom-stepper.component' |
6 | import { RegisterRoutingModule } from './register-routing.module' | 7 | import { RegisterRoutingModule } from './register-routing.module' |
7 | import { RegisterStepChannelComponent } from './register-step-channel.component' | ||
8 | import { RegisterStepTermsComponent } from './register-step-terms.component' | ||
9 | import { RegisterStepUserComponent } from './register-step-user.component' | ||
10 | import { RegisterComponent } from './register.component' | 8 | import { RegisterComponent } from './register.component' |
9 | import { RegisterStepAboutComponent, RegisterStepChannelComponent, RegisterStepTermsComponent, RegisterStepUserComponent } from './steps' | ||
11 | 10 | ||
12 | @NgModule({ | 11 | @NgModule({ |
13 | imports: [ | 12 | imports: [ |
13 | SharedMainModule, | ||
14 | RegisterRoutingModule, | 14 | RegisterRoutingModule, |
15 | 15 | ||
16 | CdkStepperModule, | 16 | CdkStepperModule, |
@@ -25,7 +25,8 @@ import { RegisterComponent } from './register.component' | |||
25 | CustomStepperComponent, | 25 | CustomStepperComponent, |
26 | RegisterStepChannelComponent, | 26 | RegisterStepChannelComponent, |
27 | RegisterStepTermsComponent, | 27 | RegisterStepTermsComponent, |
28 | RegisterStepUserComponent | 28 | RegisterStepUserComponent, |
29 | RegisterStepAboutComponent | ||
29 | ], | 30 | ], |
30 | 31 | ||
31 | exports: [ | 32 | exports: [ |
diff --git a/client/src/app/+signup/+register/steps/index.ts b/client/src/app/+signup/+register/steps/index.ts new file mode 100644 index 000000000..b5eae7468 --- /dev/null +++ b/client/src/app/+signup/+register/steps/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './register-step-about.component' | ||
2 | export * from './register-step-channel.component' | ||
3 | export * from './register-step-terms.component' | ||
4 | export * from './register-step-user.component' | ||
diff --git a/client/src/app/+signup/+register/steps/register-step-about.component.html b/client/src/app/+signup/+register/steps/register-step-about.component.html new file mode 100644 index 000000000..f93de8ce9 --- /dev/null +++ b/client/src/app/+signup/+register/steps/register-step-about.component.html | |||
@@ -0,0 +1,39 @@ | |||
1 | <div class="why"> | ||
2 | <h3 i18n>Why creating an account?</h3> | ||
3 | |||
4 | <p i18n> | ||
5 | As you probably noticed: creating an account is not necessary to watch video son {{ instanceName }}. | ||
6 | <br /> | ||
7 | However, creating an account on {{ instanceName }} will allow you to: | ||
8 | </p> | ||
9 | |||
10 | <ul> | ||
11 | <li i18n><strong>Comment</strong> videos</li> | ||
12 | <li i18n><strong>Subscribe</strong> to channels to be notified of new videos</li> | ||
13 | <li i18n>Have access to your <strong>watch history</strong></li> | ||
14 | <li *ngIf="!videoUploadDisabled" i18n>Create your channel to <strong>publish videos</strong></li> | ||
15 | </ul> | ||
16 | </div> | ||
17 | |||
18 | <div> | ||
19 | <h4 i18n>You're using Mastodon, ActivityPub or a RSS feed aggregator?</h4> | ||
20 | |||
21 | <p i18n> | ||
22 | You can already follow {{ instanceName }} using your favorite tool. | ||
23 | </p> | ||
24 | </div> | ||
25 | |||
26 | <div class="callout callout-orange callout-light"> | ||
27 | <div class="mascot-container" style="min-width: 140px"> | ||
28 | <img class="mascot" width="140px" height="160px" src="/client/assets/images/mascot/happy.svg" alt="mascot"/> | ||
29 | </div> | ||
30 | |||
31 | <div class="callout-content"> | ||
32 | <h4 i18>This website is a GAFAM alternative</h4> | ||
33 | |||
34 | <p i18n> | ||
35 | {{ instanceName }} has been created using <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">PeerTube</a>, a video creation platform developed by Framasoft. | ||
36 | <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://framasoft.org">Framasoft</a> is a french non-profit organization that offers alternatives to Big Tech's digital tools | ||
37 | </p> | ||
38 | </div> | ||
39 | </div> | ||
diff --git a/client/src/app/+signup/+register/steps/register-step-about.component.scss b/client/src/app/+signup/+register/steps/register-step-about.component.scss new file mode 100644 index 000000000..ab6d6dd4d --- /dev/null +++ b/client/src/app/+signup/+register/steps/register-step-about.component.scss | |||
@@ -0,0 +1,53 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | |||
4 | h3 { | ||
5 | font-weight: $font-bold; | ||
6 | font-size: 24px; | ||
7 | } | ||
8 | |||
9 | h4 { | ||
10 | font-size: 18px; | ||
11 | font-weight: $font-bold; | ||
12 | } | ||
13 | |||
14 | .why { | ||
15 | margin-bottom: 30px; | ||
16 | } | ||
17 | |||
18 | .callout { | ||
19 | margin: 75px auto 25px; | ||
20 | border-width: 2px; | ||
21 | display: flex; | ||
22 | |||
23 | .mascot-container { | ||
24 | position: relative; | ||
25 | |||
26 | .mascot { | ||
27 | position: absolute; | ||
28 | top: -65px; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | .callout-content { | ||
33 | margin-left: 30px; | ||
34 | |||
35 | p { | ||
36 | margin: 0; | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | |||
41 | @media screen and (max-width: $small-view) { | ||
42 | .callout { | ||
43 | margin-top: 20px; | ||
44 | |||
45 | .mascot-container { | ||
46 | display: none; | ||
47 | } | ||
48 | |||
49 | .callout-content { | ||
50 | margin-left: 0; | ||
51 | } | ||
52 | } | ||
53 | } | ||
diff --git a/client/src/app/+signup/+register/steps/register-step-about.component.ts b/client/src/app/+signup/+register/steps/register-step-about.component.ts new file mode 100644 index 000000000..9a0941016 --- /dev/null +++ b/client/src/app/+signup/+register/steps/register-step-about.component.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { ServerService } from '@app/core' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-register-step-about', | ||
6 | templateUrl: './register-step-about.component.html', | ||
7 | styleUrls: [ './register-step-about.component.scss' ] | ||
8 | }) | ||
9 | export class RegisterStepAboutComponent { | ||
10 | @Input() videoUploadDisabled: boolean | ||
11 | |||
12 | constructor (private serverService: ServerService) { | ||
13 | |||
14 | } | ||
15 | |||
16 | get instanceName () { | ||
17 | return this.serverService.getHTMLConfig().instance.name | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/+signup/+register/steps/register-step-channel.component.html b/client/src/app/+signup/+register/steps/register-step-channel.component.html new file mode 100644 index 000000000..c79256c68 --- /dev/null +++ b/client/src/app/+signup/+register/steps/register-step-channel.component.html | |||
@@ -0,0 +1,55 @@ | |||
1 | <div class="mb-5"> | ||
2 | <p i18n> | ||
3 | You want to <strong>publish videos</strong> on {{ instanceName }}? Then you need to create your first <strong>channel</strong>. | ||
4 | </p> | ||
5 | |||
6 | <p i18n> | ||
7 | You might want to <strong>create a channel by theme:</strong> for example, you can create a channel named "SweetMelodies" | ||
8 | to publish your piano concerts and another one "Ecology" in which you publish your videos talking about ecology. | ||
9 | </p> | ||
10 | |||
11 | <p i18n *ngIf="videoQuota !== -1"> | ||
12 | {{ instanceName }} administrators allow you to publish up to <strong>{{ videoQuota | bytes: 0 }} of videos</strong> on their website. | ||
13 | </p> | ||
14 | </div> | ||
15 | |||
16 | <form role="form" [formGroup]="form"> | ||
17 | |||
18 | <div class="row"> | ||
19 | |||
20 | <div class="col-md-12 col-xl-6 form-group"> | ||
21 | <label for="displayName" i18n>Channel display name</label> | ||
22 | |||
23 | <div i18n class="form-group-description">This is the name that will be publicly visible by other users.</div> | ||
24 | |||
25 | <div class="input-group"> | ||
26 | <input | ||
27 | type="text" id="displayName" i18n-placeholder placeholder="Example: Sweet Melodies" | ||
28 | formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }" | ||
29 | > | ||
30 | </div> | ||
31 | |||
32 | <div *ngIf="formErrors.displayName" class="form-error">{{ formErrors.displayName }}</div> | ||
33 | </div> | ||
34 | |||
35 | <div class="col-md-12 col-xl-6 form-group"> | ||
36 | <label for="name" i18n>Channel identifier</label> | ||
37 | |||
38 | <div i18n class="form-group-description">This is the name that will be displayed in your profile URL.</div> | ||
39 | |||
40 | <div class="input-group"> | ||
41 | <input | ||
42 | type="text" id="name" i18n-placeholder placeholder="Example: sweetmelodies24" | ||
43 | formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" | ||
44 | > | ||
45 | <div class="input-group-text">@{{ instanceHost }}</div> | ||
46 | </div> | ||
47 | |||
48 | <div *ngIf="formErrors.name" class="form-error">{{ formErrors.name }}</div> | ||
49 | |||
50 | <div *ngIf="isSameThanUsername()" class="form-error" i18n> | ||
51 | Channel identifier cannot be the same as your account name. You can click on the first step to update your account name. | ||
52 | </div> | ||
53 | </div> | ||
54 | </div> | ||
55 | </form> | ||
diff --git a/client/src/app/+signup/+register/register-step-channel.component.ts b/client/src/app/+signup/+register/steps/register-step-channel.component.ts index 1bc0ccfd3..c10b568ba 100644 --- a/client/src/app/+signup/+register/register-step-channel.component.ts +++ b/client/src/app/+signup/+register/steps/register-step-channel.component.ts | |||
@@ -9,10 +9,13 @@ import { UserSignupService } from '@app/shared/shared-users' | |||
9 | @Component({ | 9 | @Component({ |
10 | selector: 'my-register-step-channel', | 10 | selector: 'my-register-step-channel', |
11 | templateUrl: './register-step-channel.component.html', | 11 | templateUrl: './register-step-channel.component.html', |
12 | styleUrls: [ './register.component.scss' ] | 12 | styleUrls: [ './step.component.scss' ] |
13 | }) | 13 | }) |
14 | export class RegisterStepChannelComponent extends FormReactive implements OnInit { | 14 | export class RegisterStepChannelComponent extends FormReactive implements OnInit { |
15 | @Input() username: string | 15 | @Input() username: string |
16 | @Input() instanceName: string | ||
17 | @Input() videoQuota: number | ||
18 | |||
16 | @Output() formBuilt = new EventEmitter<FormGroup>() | 19 | @Output() formBuilt = new EventEmitter<FormGroup>() |
17 | 20 | ||
18 | constructor ( | 21 | constructor ( |
diff --git a/client/src/app/+signup/+register/register-step-terms.component.html b/client/src/app/+signup/+register/steps/register-step-terms.component.html index 28a6e0021..f54ca77e2 100644 --- a/client/src/app/+signup/+register/register-step-terms.component.html +++ b/client/src/app/+signup/+register/steps/register-step-terms.component.html | |||
@@ -1,18 +1,16 @@ | |||
1 | <form role="form" [formGroup]="form"> | 1 | <form role="form" [formGroup]="form"> |
2 | <div class="form-group form-group-terms"> | 2 | <div class="form-group"> |
3 | <my-peertube-checkbox inputName="terms" formControlName="terms"> | 3 | <my-peertube-checkbox inputName="terms" formControlName="terms"> |
4 | <ng-template ptTemplate="label"> | 4 | <ng-template ptTemplate="label"> |
5 | <ng-container i18n> | 5 | <ng-container i18n> |
6 | I am at least {{ minimumAge }} years old and agree | 6 | I am at least {{ minimumAge }} years old and agree |
7 | to the <a class="terms-anchor" (click)="onTermsClick($event)" href='#'>Terms</a> | 7 | to the <a class="link-orange" (click)="onTermsClick($event)" href='#'>Terms</a> |
8 | <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> | 8 | <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> |
9 | of this instance | 9 | of this instance |
10 | </ng-container> | 10 | </ng-container> |
11 | </ng-template> | 11 | </ng-template> |
12 | </my-peertube-checkbox> | 12 | </my-peertube-checkbox> |
13 | 13 | ||
14 | <div *ngIf="formErrors.terms" class="form-error"> | 14 | <div *ngIf="formErrors.terms" class="form-error">{{ formErrors.terms }}</div> |
15 | {{ formErrors.terms }} | ||
16 | </div> | ||
17 | </div> | 15 | </div> |
18 | </form> | 16 | </form> |
diff --git a/client/src/app/+signup/+register/register-step-terms.component.ts b/client/src/app/+signup/+register/steps/register-step-terms.component.ts index 20c1ae1c4..87d16696e 100644 --- a/client/src/app/+signup/+register/register-step-terms.component.ts +++ b/client/src/app/+signup/+register/steps/register-step-terms.component.ts | |||
@@ -8,7 +8,7 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | |||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-register-step-terms', | 9 | selector: 'my-register-step-terms', |
10 | templateUrl: './register-step-terms.component.html', | 10 | templateUrl: './register-step-terms.component.html', |
11 | styleUrls: [ './register.component.scss' ] | 11 | styleUrls: [ './step.component.scss' ] |
12 | }) | 12 | }) |
13 | export class RegisterStepTermsComponent extends FormReactive implements OnInit { | 13 | export class RegisterStepTermsComponent extends FormReactive implements OnInit { |
14 | @Input() hasCodeOfConduct = false | 14 | @Input() hasCodeOfConduct = false |
diff --git a/client/src/app/+signup/+register/steps/register-step-user.component.html b/client/src/app/+signup/+register/steps/register-step-user.component.html new file mode 100644 index 000000000..3e0d35006 --- /dev/null +++ b/client/src/app/+signup/+register/steps/register-step-user.component.html | |||
@@ -0,0 +1,71 @@ | |||
1 | <div class="alert pt-alert-primary" i18n *ngIf="videoUploadDisabled"> | ||
2 | Video uploads are disabled on this instance, hence your account won't be able to upload videos. | ||
3 | </div> | ||
4 | |||
5 | <form role="form" [formGroup]="form"> | ||
6 | <div class="row"> | ||
7 | |||
8 | <div class="col-md-12 col-xl-6 form-group"> | ||
9 | <label for="displayName" i18n>Public name</label> | ||
10 | |||
11 | <div class="form-group-description" i18n> | ||
12 | This is the name that will be publicly visible by other users. | ||
13 | </div> | ||
14 | |||
15 | <div class="input-group"> | ||
16 | <input | ||
17 | type="text" id="displayName" i18n-placeholder placeholder="Example: John Doe" | ||
18 | formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }" | ||
19 | > | ||
20 | </div> | ||
21 | |||
22 | <div *ngIf="formErrors.displayName" class="form-error">{{ formErrors.displayName }}</div> | ||
23 | </div> | ||
24 | |||
25 | <div class="col-md-12 col-xl-6 form-group"> | ||
26 | <label for="username" i18n>Username</label> | ||
27 | |||
28 | <div class="form-group-description" i18n> | ||
29 | This is the name that will be displayed in your profile URL. | ||
30 | </div> | ||
31 | |||
32 | <div class="input-group"> | ||
33 | <input | ||
34 | type="text" id="username" i18n-placeholder placeholder="Example: john_doe58" | ||
35 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" | ||
36 | > | ||
37 | <span class="input-group-text">@{{ instanceHost }}</span> | ||
38 | </div> | ||
39 | |||
40 | <div *ngIf="formErrors.username" class="form-error">{{ formErrors.username }}</div> | ||
41 | </div> | ||
42 | </div> | ||
43 | |||
44 | <div class="row"> | ||
45 | <div class="col-md-12 col-xl-6 form-group"> | ||
46 | <label for="email" i18n>Email</label> | ||
47 | |||
48 | <div *ngIf="requiresEmailVerification" class="form-group-description" i18n> | ||
49 | This email address will be used to validate your account. | ||
50 | </div> | ||
51 | |||
52 | <input | ||
53 | type="text" id="email" i18n-placeholder placeholder="Example: john@example.com" | ||
54 | formControlName="email" class="form-control" [ngClass]="{ 'input-error': formErrors['email'] }" | ||
55 | > | ||
56 | |||
57 | <div *ngIf="formErrors.email" class="form-error">{{ formErrors.email }}</div> | ||
58 | </div> | ||
59 | |||
60 | <div class="col-md-12 col-xl-6 form-group"> | ||
61 | <label for="password" i18n>Password</label> | ||
62 | |||
63 | <div class="form-group-description">{{ getMinPasswordLengthMessage() }}</div> | ||
64 | |||
65 | <my-input-text | ||
66 | formControlName="password" inputId="password" | ||
67 | [formError]="formErrors['password']" autocomplete="new-password" | ||
68 | ></my-input-text> | ||
69 | </div> | ||
70 | </div> | ||
71 | </form> | ||
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/steps/register-step-user.component.ts index 92ddfca2e..b89e38a28 100644 --- a/client/src/app/+signup/+register/register-step-user.component.ts +++ b/client/src/app/+signup/+register/steps/register-step-user.component.ts | |||
@@ -14,10 +14,11 @@ import { UserSignupService } from '@app/shared/shared-users' | |||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-register-step-user', | 15 | selector: 'my-register-step-user', |
16 | templateUrl: './register-step-user.component.html', | 16 | templateUrl: './register-step-user.component.html', |
17 | styleUrls: [ './register.component.scss' ] | 17 | styleUrls: [ './step.component.scss' ] |
18 | }) | 18 | }) |
19 | export class RegisterStepUserComponent extends FormReactive implements OnInit { | 19 | export class RegisterStepUserComponent extends FormReactive implements OnInit { |
20 | @Input() videoUploadDisabled = false | 20 | @Input() videoUploadDisabled = false |
21 | @Input() requiresEmailVerification = false | ||
21 | 22 | ||
22 | @Output() formBuilt = new EventEmitter<FormGroup>() | 23 | @Output() formBuilt = new EventEmitter<FormGroup>() |
23 | 24 | ||
@@ -49,6 +50,10 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit { | |||
49 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) | 50 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) |
50 | } | 51 | } |
51 | 52 | ||
53 | getMinPasswordLengthMessage () { | ||
54 | return USER_PASSWORD_VALIDATOR.MESSAGES.minlength | ||
55 | } | ||
56 | |||
52 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { | 57 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { |
53 | const username = this.form.value['username'] || '' | 58 | const username = this.form.value['username'] || '' |
54 | 59 | ||
diff --git a/client/src/app/+signup/+register/steps/step.component.scss b/client/src/app/+signup/+register/steps/step.component.scss new file mode 100644 index 000000000..35cfdae91 --- /dev/null +++ b/client/src/app/+signup/+register/steps/step.component.scss | |||
@@ -0,0 +1,27 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | |||
4 | input:not([type=submit]) { | ||
5 | @include peertube-input-text(100%); | ||
6 | display: block; | ||
7 | |||
8 | &#username, | ||
9 | &#name { | ||
10 | width: auto !important; | ||
11 | flex-grow: 1; | ||
12 | } | ||
13 | } | ||
14 | |||
15 | input[type=submit], | ||
16 | button { | ||
17 | @include peertube-button; | ||
18 | } | ||
19 | |||
20 | label { | ||
21 | font-size: 18px; | ||
22 | margin-bottom: 5px; | ||
23 | } | ||
24 | |||
25 | .row { | ||
26 | margin-bottom: 30px; | ||
27 | } | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html index ece9d1022..023082f61 100644 --- a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html | |||
@@ -1,21 +1,21 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <div i18n class="title-page title-page-single"> | 2 | <h1 i18n class="title-page">Request email for account verification</h1> |
3 | Request email for account verification | ||
4 | </div> | ||
5 | 3 | ||
6 | <form *ngIf="requiresEmailVerification; else emailVerificationNotRequired" role="form" (ngSubmit)="askSendVerifyEmail()" [formGroup]="form"> | 4 | <form *ngIf="requiresEmailVerification; else emailVerificationNotRequired" role="form" (ngSubmit)="askSendVerifyEmail()" [formGroup]="form"> |
7 | <div class="form-group"> | 5 | <div class="form-group"> |
8 | <label i18n for="verify-email-email">Email</label> | 6 | <label i18n for="verify-email-email">Email</label> |
7 | |||
9 | <input | 8 | <input |
10 | type="email" id="verify-email-email" i18n-placeholder placeholder="Email address" required | 9 | type="email" id="verify-email-email" i18n-placeholder placeholder="Email address" required |
11 | formControlName="verify-email-email" class="form-control" [ngClass]="{ 'input-error': formErrors['verify-email-email'] }" | 10 | formControlName="verify-email-email" class="form-control" [ngClass]="{ 'input-error': formErrors['verify-email-email'] }" |
12 | > | 11 | > |
13 | <div *ngIf="formErrors['verify-email-email']" class="form-error"> | 12 | |
14 | {{ formErrors['verify-email-email'] }} | 13 | <div *ngIf="formErrors['verify-email-email']" class="form-error">{{ formErrors['verify-email-email'] }}</div> |
15 | </div> | ||
16 | </div> | 14 | </div> |
17 | <input type="submit" i18n-value value="Send verification email" [disabled]="!form.valid"> | 15 | |
16 | <input class="peertube-button orange-button" type="submit" i18n-value value="Send verification email" [disabled]="!form.valid"> | ||
18 | </form> | 17 | </form> |
18 | |||
19 | <ng-template #emailVerificationNotRequired> | 19 | <ng-template #emailVerificationNotRequired> |
20 | <div i18n>This instance does not require email verification.</div> | 20 | <div i18n>This instance does not require email verification.</div> |
21 | </ng-template> | 21 | </ng-template> |
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss index 5494e29c6..c542f3d8a 100644 --- a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss | |||
@@ -1,12 +1,8 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | input:not([type=submit]) { | 4 | input[type=email] { |
5 | @include peertube-input-text(340px); | 5 | @include peertube-input-text(340px); |
6 | display: block; | ||
7 | } | ||
8 | 6 | ||
9 | input[type=submit] { | 7 | display: block; |
10 | @include peertube-button; | ||
11 | @include orange-button; | ||
12 | } | 8 | } |
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html index 47519c943..122f3c28c 100644 --- a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html +++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html | |||
@@ -1,18 +1,14 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <div i18n class="title-page title-page-single"> | 2 | <h1 i18n class="title-page">Verify account email confirmation</h1> |
3 | Verify account email confirmation | ||
4 | </div> | ||
5 | 3 | ||
6 | <my-signup-success i18n *ngIf="!isPendingEmail && success" message="Your email has been verified and you may now login."> | 4 | <my-signup-success i18n *ngIf="!isPendingEmail && success" [requiresEmailVerification]="false"> |
7 | </my-signup-success> | 5 | </my-signup-success> |
8 | 6 | ||
9 | <div i18n class="alert alert-success" *ngIf="isPendingEmail && success"> | 7 | <div i18n class="alert alert-success" *ngIf="isPendingEmail && success">Email updated.</div> |
10 | Email updated. | ||
11 | </div> | ||
12 | 8 | ||
13 | <div *ngIf="failed"> | 9 | <div class="alert alert-danger" *ngIf="failed"> |
14 | <span i18n>An error occurred.</span> | 10 | <span i18n>An error occurred.</span> |
15 | 11 | ||
16 | <a i18n routerLink="/verify-account/ask-send-email" [queryParams]="{ isPendingEmail: isPendingEmail }">Request new verification email.</a> | 12 | <a i18n class="ms-1 link-orange" routerLink="/verify-account/ask-send-email" [queryParams]="{ isPendingEmail: isPendingEmail }">Request new verification email</a> |
17 | </div> | 13 | </div> |
18 | </div> | 14 | </div> |
diff --git a/client/src/app/+signup/+verify-account/verify-account-routing.module.ts b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts index 1bc636345..d53a5ed78 100644 --- a/client/src/app/+signup/+verify-account/verify-account-routing.module.ts +++ b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts | |||
@@ -21,7 +21,7 @@ const verifyAccountRoutes: Routes = [ | |||
21 | component: VerifyAccountAskSendEmailComponent, | 21 | component: VerifyAccountAskSendEmailComponent, |
22 | data: { | 22 | data: { |
23 | meta: { | 23 | meta: { |
24 | title: $localize`Ask to send an email to verify you account` | 24 | title: $localize`Ask to send an email to verify your account` |
25 | } | 25 | } |
26 | } | 26 | } |
27 | } | 27 | } |
diff --git a/client/src/app/+signup/shared/shared-signup.module.ts b/client/src/app/+signup/shared/shared-signup.module.ts index f8b224c71..0aa08f3e2 100644 --- a/client/src/app/+signup/shared/shared-signup.module.ts +++ b/client/src/app/+signup/shared/shared-signup.module.ts | |||
@@ -3,6 +3,8 @@ 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 { SharedUsersModule } from '@app/shared/shared-users' | 5 | import { SharedUsersModule } from '@app/shared/shared-users' |
6 | import { SignupMascotComponent } from './signup-mascot.component' | ||
7 | import { SignupStepTitleComponent } from './signup-step-title.component' | ||
6 | import { SignupSuccessComponent } from './signup-success.component' | 8 | import { SignupSuccessComponent } from './signup-success.component' |
7 | 9 | ||
8 | @NgModule({ | 10 | @NgModule({ |
@@ -14,7 +16,9 @@ import { SignupSuccessComponent } from './signup-success.component' | |||
14 | ], | 16 | ], |
15 | 17 | ||
16 | declarations: [ | 18 | declarations: [ |
17 | SignupSuccessComponent | 19 | SignupSuccessComponent, |
20 | SignupStepTitleComponent, | ||
21 | SignupMascotComponent | ||
18 | ], | 22 | ], |
19 | 23 | ||
20 | exports: [ | 24 | exports: [ |
@@ -22,7 +26,9 @@ import { SignupSuccessComponent } from './signup-success.component' | |||
22 | SharedFormModule, | 26 | SharedFormModule, |
23 | SharedGlobalIconModule, | 27 | SharedGlobalIconModule, |
24 | 28 | ||
25 | SignupSuccessComponent | 29 | SignupSuccessComponent, |
30 | SignupStepTitleComponent, | ||
31 | SignupMascotComponent | ||
26 | ], | 32 | ], |
27 | 33 | ||
28 | providers: [ | 34 | providers: [ |
diff --git a/client/src/app/+signup/shared/signup-mascot.component.scss b/client/src/app/+signup/shared/signup-mascot.component.scss new file mode 100644 index 000000000..5eebfb014 --- /dev/null +++ b/client/src/app/+signup/shared/signup-mascot.component.scss | |||
@@ -0,0 +1,11 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | |||
4 | .root { | ||
5 | display: inline-block; | ||
6 | width: 270px; | ||
7 | } | ||
8 | |||
9 | div ::ng-deep svg { | ||
10 | color: pvar(--mainColor); | ||
11 | } | ||
diff --git a/client/src/app/+signup/shared/signup-mascot.component.ts b/client/src/app/+signup/shared/signup-mascot.component.ts new file mode 100644 index 000000000..a96ccffee --- /dev/null +++ b/client/src/app/+signup/shared/signup-mascot.component.ts | |||
@@ -0,0 +1,29 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { DomSanitizer } from '@angular/platform-browser' | ||
3 | |||
4 | const images = { | ||
5 | about: require('!!raw-loader?!../../../assets/images/mascot/register/about.svg').default, | ||
6 | terms: require('!!raw-loader?!../../../assets/images/mascot/register/terms.svg').default, | ||
7 | success: require('!!raw-loader?!../../../assets/images/mascot/register/success.svg').default, | ||
8 | channel: require('!!raw-loader?!../../../assets/images/mascot/register/channel.svg').default, | ||
9 | account: require('!!raw-loader?!../../../assets/images/mascot/register/account.svg').default | ||
10 | } | ||
11 | |||
12 | export type MascotImageName = keyof typeof images | ||
13 | |||
14 | @Component({ | ||
15 | selector: 'my-signup-mascot', | ||
16 | styleUrls: [ './signup-mascot.component.scss' ], | ||
17 | template: `<div class="root" [innerHTML]="html"></div>` | ||
18 | }) | ||
19 | export class SignupMascotComponent { | ||
20 | @Input() imageName: MascotImageName | ||
21 | |||
22 | constructor (private sanitize: DomSanitizer) { | ||
23 | |||
24 | } | ||
25 | |||
26 | get html () { | ||
27 | return this.sanitize.bypassSecurityTrustHtml(images[this.imageName]) | ||
28 | } | ||
29 | } | ||
diff --git a/client/src/app/+signup/shared/signup-step-title.component.html b/client/src/app/+signup/shared/signup-step-title.component.html new file mode 100644 index 000000000..9cf4c4826 --- /dev/null +++ b/client/src/app/+signup/shared/signup-step-title.component.html | |||
@@ -0,0 +1,9 @@ | |||
1 | <div class="step-content-title"> | ||
2 | <my-signup-mascot [imageName]="mascotImageName"></my-signup-mascot> | ||
3 | |||
4 | <h2> | ||
5 | <ng-content></ng-content> | ||
6 | </h2> | ||
7 | |||
8 | <div class="step-content-title-separator"></div> | ||
9 | </div> | ||
diff --git a/client/src/app/+signup/shared/signup-step-title.component.scss b/client/src/app/+signup/shared/signup-step-title.component.scss new file mode 100644 index 000000000..1e0cb2440 --- /dev/null +++ b/client/src/app/+signup/shared/signup-step-title.component.scss | |||
@@ -0,0 +1,23 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | |||
4 | .step-content-title { | ||
5 | text-align: center; | ||
6 | margin: auto; | ||
7 | margin-bottom: 45px; | ||
8 | |||
9 | h2 { | ||
10 | font-size: 32px; | ||
11 | font-weight: normal; | ||
12 | max-width: 300px; | ||
13 | margin: 15px auto 0; | ||
14 | } | ||
15 | } | ||
16 | |||
17 | .step-content-title-separator { | ||
18 | height: 6px; | ||
19 | width: 60px; | ||
20 | border-radius: 4px; | ||
21 | background-color: pvar(--mainColor); | ||
22 | margin: 5px auto 0; | ||
23 | } | ||
diff --git a/client/src/app/+signup/shared/signup-step-title.component.ts b/client/src/app/+signup/shared/signup-step-title.component.ts new file mode 100644 index 000000000..9664eb7f3 --- /dev/null +++ b/client/src/app/+signup/shared/signup-step-title.component.ts | |||
@@ -0,0 +1,12 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { MascotImageName } from './signup-mascot.component' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-signup-step-title', | ||
6 | templateUrl: './signup-step-title.component.html', | ||
7 | styleUrls: [ './signup-step-title.component.scss' ] | ||
8 | }) | ||
9 | export class SignupStepTitleComponent { | ||
10 | @Input() mascotImageName: MascotImageName | ||
11 | |||
12 | } | ||
diff --git a/client/src/app/+signup/shared/signup-success.component.html b/client/src/app/+signup/shared/signup-success.component.html index d66e8b568..c14889c72 100644 --- a/client/src/app/+signup/shared/signup-success.component.html +++ b/client/src/app/+signup/shared/signup-success.component.html | |||
@@ -1,20 +1,22 @@ | |||
1 | <!-- Thanks: Amit Singh Sansoya from https://codepen.io/amit3200/pen/zWMJOO --> | 1 | <my-signup-step-title mascotImageName="success" i18n> |
2 | <strong>Welcome</strong> | ||
3 | <div>on {{ instanceName }}</div> | ||
4 | </my-signup-step-title> | ||
2 | 5 | ||
3 | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2"> | 6 | <div class="alert pt-alert-primary"> |
4 | <circle class="path circle" fill="none" stroke="#73AF55" stroke-width="6" stroke-miterlimit="10" cx="65.1" cy="65.1" r="62.1"/> | 7 | <p i18n>Your account has been created!</p> |
5 | <polyline class="path check" fill="none" stroke="#73AF55" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" points="100.2,40.2 51.5,88.8 29.8,67.5 "/> | ||
6 | </svg> | ||
7 | 8 | ||
8 | <p i18n class="bottom-message">Welcome to PeerTube!</p> | 9 | <p i18n *ngIf="requiresEmailVerification"> |
9 | 10 | <strong>Check your emails</strong> to validate your account and complete your inscription. | |
10 | <div *ngIf="message" class="alert alert-success"> | ||
11 | <p>{{ message }}</p> | ||
12 | |||
13 | <p i18n> | ||
14 | If you need help to use PeerTube, you can have a look at the <a href="https://docs.joinpeertube.org/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>. | ||
15 | </p> | 11 | </p> |
16 | 12 | ||
17 | <p i18n> | 13 | <ng-container *ngIf="!requiresEmailVerification"> |
18 | To help moderators and other users to know <strong>who you are</strong>, don't forget to <a routerLink="/my-account/settings">set up your account profile</a> by adding an <strong>avatar</strong> and a <strong>description</strong>. | 14 | <p i18n> |
19 | </p> | 15 | If you need help to use PeerTube, you can have a look at the <a class="link-orange" href="https://docs.joinpeertube.org/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>. |
16 | </p> | ||
17 | |||
18 | <p i18n> | ||
19 | To help moderators and other users to know <strong>who you are</strong>, don't forget to <a class="link-orange" routerLink="/my-account/settings">set up your account profile</a> by adding an <strong>avatar</strong> and a <strong>description</strong>. | ||
20 | </p> | ||
21 | </ng-container> | ||
20 | </div> | 22 | </div> |
diff --git a/client/src/app/+signup/shared/signup-success.component.scss b/client/src/app/+signup/shared/signup-success.component.scss index b302366e2..918349ba0 100644 --- a/client/src/app/+signup/shared/signup-success.component.scss +++ b/client/src/app/+signup/shared/signup-success.component.scss | |||
@@ -1,54 +1,6 @@ | |||
1 | svg { | ||
2 | width: 100px; | ||
3 | display: block; | ||
4 | margin: 40px auto 0; | ||
5 | } | ||
6 | |||
7 | .path { | ||
8 | stroke-dasharray: 1000; | ||
9 | stroke-dashoffset: 0; | ||
10 | |||
11 | &.circle { | ||
12 | animation: dash .9s ease-in-out; | ||
13 | } | ||
14 | |||
15 | &.line { | ||
16 | stroke-dashoffset: 1000; | ||
17 | animation: dash .9s .35s ease-in-out forwards; | ||
18 | } | ||
19 | |||
20 | &.check { | ||
21 | stroke-dashoffset: -100; | ||
22 | animation: dash-check .9s .35s ease-in-out forwards; | ||
23 | } | ||
24 | } | ||
25 | |||
26 | .bottom-message { | ||
27 | text-align: center; | ||
28 | margin: 20px 0 60px; | ||
29 | font-size: 1.25em; | ||
30 | color: #73AF55; | ||
31 | } | ||
32 | |||
33 | .alert { | 1 | .alert { |
34 | font-size: 15px; | 2 | font-size: 18px; |
3 | max-width: 900px; | ||
35 | text-align: center; | 4 | text-align: center; |
36 | } | 5 | margin: auto; |
37 | |||
38 | @keyframes dash { | ||
39 | 0% { | ||
40 | stroke-dashoffset: 1000; | ||
41 | } | ||
42 | 100% { | ||
43 | stroke-dashoffset: 0; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | @keyframes dash-check { | ||
48 | 0% { | ||
49 | stroke-dashoffset: -100; | ||
50 | } | ||
51 | 100% { | ||
52 | stroke-dashoffset: 900; | ||
53 | } | ||
54 | } | 6 | } |
diff --git a/client/src/app/+signup/shared/signup-success.component.ts b/client/src/app/+signup/shared/signup-success.component.ts index 19fb5922a..a03f3819d 100644 --- a/client/src/app/+signup/shared/signup-success.component.ts +++ b/client/src/app/+signup/shared/signup-success.component.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { ServerService } from '@app/core' | ||
2 | 3 | ||
3 | @Component({ | 4 | @Component({ |
4 | selector: 'my-signup-success', | 5 | selector: 'my-signup-success', |
@@ -6,5 +7,13 @@ import { Component, Input } from '@angular/core' | |||
6 | styleUrls: [ './signup-success.component.scss' ] | 7 | styleUrls: [ './signup-success.component.scss' ] |
7 | }) | 8 | }) |
8 | export class SignupSuccessComponent { | 9 | export class SignupSuccessComponent { |
9 | @Input() message: string | 10 | @Input() requiresEmailVerification: boolean |
11 | |||
12 | constructor (private serverService: ServerService) { | ||
13 | |||
14 | } | ||
15 | |||
16 | get instanceName () { | ||
17 | return this.serverService.getHTMLConfig().instance.name | ||
18 | } | ||
10 | } | 19 | } |
diff --git a/client/src/app/+stats/video/video-stats.component.html b/client/src/app/+stats/video/video-stats.component.html index adfe095a3..242a5a7a2 100644 --- a/client/src/app/+stats/video/video-stats.component.html +++ b/client/src/app/+stats/video/video-stats.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <h1 class="title-page title-page-single" i18n>{{ video.name }}</h1> | 2 | <h1 class="title-page" i18n>{{ video.name }}</h1> |
3 | 3 | ||
4 | <div class="stats-embed"> | 4 | <div class="stats-embed"> |
5 | <div class="global-stats"> | 5 | <div class="global-stats"> |
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html index fa58963ce..dd0b10d66 100644 --- a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html +++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <div i18n class="title-page title-page-single" *ngIf="pagination.totalItems"> | 2 | <h1 i18n class="title-page" *ngIf="pagination.totalItems"> |
3 | Created {pagination.totalItems, plural, =1 {1 playlist} other {{{ pagination.totalItems }} playlists}} | 3 | Created {pagination.totalItems, plural, =1 {1 playlist} other {{{ pagination.totalItems }} playlists}} |
4 | </div> | 4 | </h1> |
5 | 5 | ||
6 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div> | 6 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div> |
7 | 7 | ||
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html index 212e2f867..d92aa072d 100644 --- a/client/src/app/+video-channels/video-channels.component.html +++ b/client/src/app/+video-channels/video-channels.component.html | |||
@@ -23,7 +23,7 @@ | |||
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]="ownerAccount" [internalHref]="getAccountUrl()"></my-actor-avatar> | 26 | <my-actor-avatar class="account-avatar" [actor]="ownerAccount" actorType="account" [internalHref]="getAccountUrl()"></my-actor-avatar> |
27 | 27 | ||
28 | <div class="actor-info"> | 28 | <div class="actor-info"> |
29 | <h4> | 29 | <h4> |
@@ -51,7 +51,7 @@ | |||
51 | </ng-template> | 51 | </ng-template> |
52 | 52 | ||
53 | <div class="channel-avatar-row"> | 53 | <div class="channel-avatar-row"> |
54 | <my-actor-avatar class="main-avatar" [channel]="videoChannel"></my-actor-avatar> | 54 | <my-actor-avatar class="main-avatar" [actor]="videoChannel" actorType="channel"></my-actor-avatar> |
55 | 55 | ||
56 | <div> | 56 | <div> |
57 | <div class="section-label" i18n>VIDEO CHANNEL</div> | 57 | <div class="section-label" i18n>VIDEO CHANNEL</div> |
@@ -67,15 +67,15 @@ | |||
67 | <button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()" | 67 | <button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()" |
68 | class="btn btn-outline-secondary btn-sm copy-button" title="Copy channel handle" i18n-title | 68 | class="btn btn-outline-secondary btn-sm copy-button" title="Copy channel handle" i18n-title |
69 | > | 69 | > |
70 | <span class="glyphicon glyphicon-duplicate"></span> | 70 | <my-global-icon iconName="copy"></my-global-icon> |
71 | </button> | 71 | </button> |
72 | </div> | 72 | </div> |
73 | 73 | ||
74 | <div class="actor-counters"> | 74 | <div class="actor-counters"> |
75 | <span i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</span> | 75 | <span i18n>{videoChannel.followersCount, plural, =0 {No subscribers} =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</span> |
76 | 76 | ||
77 | <span class="videos-count" *ngIf="channelVideosCount !== undefined" i18n> | 77 | <span class="videos-count" *ngIf="channelVideosCount !== undefined" i18n> |
78 | {channelVideosCount, plural, =1 {1 videos} other {{{ channelVideosCount }} videos}} | 78 | {channelVideosCount, plural, =0 {No videos} =1 {1 video} other {{{ channelVideosCount }} videos}} |
79 | </span> | 79 | </span> |
80 | </div> | 80 | </div> |
81 | </div> | 81 | </div> |
@@ -113,7 +113,7 @@ | |||
113 | 113 | ||
114 | <div class="links"> | 114 | <div class="links"> |
115 | <ng-template #linkTemplate let-item="item"> | 115 | <ng-template #linkTemplate let-item="item"> |
116 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> | 116 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="sub-menu-entry">{{ item.label }}</a> |
117 | </ng-template> | 117 | </ng-template> |
118 | 118 | ||
119 | <my-list-overflow [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow> | 119 | <my-list-overflow [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow> |
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss index c00dacae5..945d9a13d 100644 --- a/client/src/app/+video-channels/video-channels.component.scss +++ b/client/src/app/+video-channels/video-channels.component.scss | |||
@@ -150,7 +150,13 @@ | |||
150 | } | 150 | } |
151 | 151 | ||
152 | .copy-button { | 152 | .copy-button { |
153 | @include margin-left(3px); | ||
154 | |||
153 | border: 0; | 155 | border: 0; |
156 | |||
157 | my-global-icon { | ||
158 | width: 15px; | ||
159 | } | ||
154 | } | 160 | } |
155 | 161 | ||
156 | @media screen and (max-width: 1400px) { | 162 | @media screen and (max-width: 1400px) { |
diff --git a/client/src/app/+video-studio/edit/video-studio-edit.component.html b/client/src/app/+video-studio/edit/video-studio-edit.component.html index acfc1a452..fe74062d2 100644 --- a/client/src/app/+video-studio/edit/video-studio-edit.component.html +++ b/client/src/app/+video-studio/edit/video-studio-edit.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <h1 class="title-page title-page-single" i18n>Studio for {{ video.name }}</h1> | 2 | <h1 class="title-page" i18n>Studio for {{ video.name }}</h1> |
3 | 3 | ||
4 | <div class="columns"> | 4 | <div class="columns"> |
5 | <form role="form" [formGroup]="form"> | 5 | <form role="form" [formGroup]="form"> |
diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss index 4ce2c6758..c0b670c65 100644 --- a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss +++ b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss | |||
@@ -1,11 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | .caption-file { | 4 | .caption-file { |
10 | margin-top: 20px; | 5 | margin-top: 20px; |
11 | width: max-content; | 6 | width: max-content; |
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html index 44004eb21..650448a74 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html | |||
@@ -51,11 +51,10 @@ | |||
51 | </ng-template> | 51 | </ng-template> |
52 | </my-help> | 52 | </my-help> |
53 | 53 | ||
54 | <my-markdown-textarea [truncate]="250" formControlName="description" [markdownVideo]="videoToUpdate"></my-markdown-textarea> | 54 | <my-markdown-textarea |
55 | 55 | formControlName="description" [markdownVideo]="videoToUpdate" | |
56 | <div *ngIf="formErrors.description" class="form-error"> | 56 | [formError]="formErrors.description" [truncate]="250" |
57 | {{ formErrors.description }} | 57 | ></my-markdown-textarea> |
58 | </div> | ||
59 | </div> | 58 | </div> |
60 | </div> | 59 | </div> |
61 | 60 | ||
@@ -237,23 +236,23 @@ | |||
237 | <ng-template ngbNavContent> | 236 | <ng-template ngbNavContent> |
238 | <div class="row live-settings"> | 237 | <div class="row live-settings"> |
239 | <div class="col-md-12"> | 238 | <div class="col-md-12"> |
240 | <div class="alert alert-info"> | 239 | <div class="alert pt-alert-primary"> |
241 | <my-live-documentation-link></my-live-documentation-link> | 240 | <my-live-documentation-link></my-live-documentation-link> |
242 | </div> | 241 | </div> |
243 | 242 | ||
244 | <div *ngIf="liveVideo.rtmpUrl" class="form-group"> | 243 | <div *ngIf="liveVideo.rtmpUrl" class="form-group"> |
245 | <label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label> | 244 | <label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label> |
246 | <my-input-toggle-hidden inputId="liveVideoRTMPUrl" [value]="liveVideo.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | 245 | <my-input-text inputId="liveVideoRTMPUrl" [value]="liveVideo.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> |
247 | </div> | 246 | </div> |
248 | 247 | ||
249 | <div *ngIf="liveVideo.rtmpsUrl" class="form-group"> | 248 | <div *ngIf="liveVideo.rtmpsUrl" class="form-group"> |
250 | <label for="liveVideoRTMPSUrl" i18n>Live RTMPS Url</label> | 249 | <label for="liveVideoRTMPSUrl" i18n>Live RTMPS Url</label> |
251 | <my-input-toggle-hidden inputId="liveVideoRTMPSUrl" [value]="liveVideo.rtmpsUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | 250 | <my-input-text inputId="liveVideoRTMPSUrl" [value]="liveVideo.rtmpsUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> |
252 | </div> | 251 | </div> |
253 | 252 | ||
254 | <div class="form-group"> | 253 | <div class="form-group"> |
255 | <label for="liveVideoStreamKey" i18n>Live stream key</label> | 254 | <label for="liveVideoStreamKey" i18n>Live stream key</label> |
256 | <my-input-toggle-hidden inputId="liveVideoStreamKey" [value]="liveVideo.streamKey" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden> | 255 | <my-input-text inputId="liveVideoStreamKey" [value]="liveVideo.streamKey" [withCopy]="true" [readonly]="true"></my-input-text> |
257 | 256 | ||
258 | <div class="form-group-description" i18n>⚠️ Never share your stream key with anyone.</div> | 257 | <div class="form-group-description" i18n>⚠️ Never share your stream key with anyone.</div> |
259 | </div> | 258 | </div> |
@@ -332,17 +331,30 @@ | |||
332 | </ng-container> | 331 | </ng-container> |
333 | </ng-template> | 332 | </ng-template> |
334 | </my-help> | 333 | </my-help> |
334 | |||
335 | <my-markdown-textarea | 335 | <my-markdown-textarea |
336 | id="support" formControlName="support" markdownType="enhanced" | 336 | id="support" formControlName="support" markdownType="enhanced" |
337 | [classes]="{ 'input-error': formErrors['support'] }" | 337 | [formError]="formErrors['support']" |
338 | ></my-markdown-textarea> | 338 | ></my-markdown-textarea> |
339 | <div *ngIf="formErrors.support" class="form-error"> | ||
340 | {{ formErrors.support }} | ||
341 | </div> | ||
342 | </div> | 339 | </div> |
343 | </div> | 340 | </div> |
344 | 341 | ||
345 | <div class="col-md-12 col-xl-4"> | 342 | <div class="col-md-12 col-xl-4"> |
343 | |||
344 | <div *ngIf="videoSource" class="form-group"> | ||
345 | <label i18n for="filename">Filename</label> | ||
346 | |||
347 | <my-help> | ||
348 | <ng-template ptTemplate="preHtml"> | ||
349 | <ng-container i18n> | ||
350 | Name of the uploaded file | ||
351 | </ng-container> | ||
352 | </ng-template> | ||
353 | </my-help> | ||
354 | |||
355 | <input type="text" [disabled]="true" id="filename" class="form-control" [value]="videoSource.filename" /> | ||
356 | </div> | ||
357 | |||
346 | <div class="form-group originally-published-at"> | 358 | <div class="form-group originally-published-at"> |
347 | <label i18n for="originallyPublishedAt">Original publication date</label> | 359 | <label i18n for="originallyPublishedAt">Original publication date</label> |
348 | <my-help> | 360 | <my-help> |
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.scss b/client/src/app/+videos/+video-edit/shared/video-edit.component.scss index 5344e5431..e8a6c6e42 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.scss | |||
@@ -1,25 +1,10 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label, | ||
5 | my-dynamic-form-field ::ng-deep label { | ||
6 | font-weight: $font-regular; | ||
7 | font-size: 100%; | ||
8 | } | ||
9 | |||
10 | .peertube-select-container { | 4 | .peertube-select-container { |
11 | @include peertube-select-container(auto); | 5 | @include peertube-select-container(auto); |
12 | } | 6 | } |
13 | 7 | ||
14 | .title-page a { | ||
15 | color: pvar(--mainForegroundColor); | ||
16 | |||
17 | &:hover { | ||
18 | text-decoration: none; | ||
19 | opacity: .8; | ||
20 | } | ||
21 | } | ||
22 | |||
23 | my-peertube-checkbox { | 8 | my-peertube-checkbox { |
24 | display: block; | 9 | display: block; |
25 | margin-bottom: 1rem; | 10 | margin-bottom: 1rem; |
@@ -33,22 +18,10 @@ my-peertube-checkbox { | |||
33 | height: 100%; | 18 | height: 100%; |
34 | min-height: 300px; | 19 | min-height: 300px; |
35 | 20 | ||
36 | .form-group { | ||
37 | margin-bottom: 25px; | ||
38 | } | ||
39 | |||
40 | input { | 21 | input { |
41 | @include peertube-input-text(100%); | 22 | @include peertube-input-text(100%); |
42 | display: block; | 23 | display: block; |
43 | } | 24 | } |
44 | |||
45 | .label-tags + span { | ||
46 | font-size: 15px; | ||
47 | } | ||
48 | |||
49 | .advanced-settings .form-group { | ||
50 | margin-bottom: 20px; | ||
51 | } | ||
52 | } | 25 | } |
53 | 26 | ||
54 | .captions-header { | 27 | .captions-header { |
@@ -79,7 +52,6 @@ my-peertube-checkbox { | |||
79 | .caption-entry-label { | 52 | .caption-entry-label { |
80 | @include margin-right(20px); | 53 | @include margin-right(20px); |
81 | 54 | ||
82 | font-size: 15px; | ||
83 | font-weight: bold; | 55 | font-weight: bold; |
84 | width: 150px; | 56 | width: 150px; |
85 | } | 57 | } |
@@ -108,7 +80,6 @@ my-peertube-checkbox { | |||
108 | 80 | ||
109 | .no-caption { | 81 | .no-caption { |
110 | text-align: center; | 82 | text-align: center; |
111 | font-size: 15px; | ||
112 | } | 83 | } |
113 | 84 | ||
114 | .submit-container { | 85 | .submit-container { |
@@ -119,7 +90,6 @@ my-peertube-checkbox { | |||
119 | 90 | ||
120 | display: inline-block; | 91 | display: inline-block; |
121 | color: pvar(--greyForegroundColor); | 92 | color: pvar(--greyForegroundColor); |
122 | font-size: 15px; | ||
123 | } | 93 | } |
124 | } | 94 | } |
125 | 95 | ||
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index 16b964482..c74ef5731 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts | |||
@@ -37,6 +37,7 @@ import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' | |||
37 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 37 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
38 | import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' | 38 | import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' |
39 | import { VideoEditType } from './video-edit.type' | 39 | import { VideoEditType } from './video-edit.type' |
40 | import { VideoSource } from '@shared/models/videos/video-source' | ||
40 | 41 | ||
41 | type VideoLanguages = VideoConstant<string> & { group?: string } | 42 | type VideoLanguages = VideoConstant<string> & { group?: string } |
42 | type PluginField = { | 43 | type PluginField = { |
@@ -61,6 +62,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
61 | @Input() forbidScheduledPublication = true | 62 | @Input() forbidScheduledPublication = true |
62 | 63 | ||
63 | @Input() videoCaptions: VideoCaptionWithPathEdit[] = [] | 64 | @Input() videoCaptions: VideoCaptionWithPathEdit[] = [] |
65 | @Input() videoSource: VideoSource | ||
64 | 66 | ||
65 | @Input() waitTranscodingEnabled = true | 67 | @Input() waitTranscodingEnabled = true |
66 | @Input() type: VideoEditType | 68 | @Input() type: VideoEditType |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html index ecec6045b..f537b939f 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html | |||
@@ -44,7 +44,7 @@ | |||
44 | {{ error }} | 44 | {{ error }} |
45 | </div> | 45 | </div> |
46 | 46 | ||
47 | <div class="alert alert-info" i18n *ngIf="isInUpdateForm && getMaxLiveDuration() >= 0"> | 47 | <div class="alert pt-alert-primary" i18n *ngIf="isInUpdateForm && getMaxLiveDuration() >= 0"> |
48 | Max live duration is {{ getMaxLiveDuration() | myDurationFormatter }}. | 48 | Max live duration is {{ getMaxLiveDuration() | myDurationFormatter }}. |
49 | If your live reaches this limit, it will be automatically terminated. | 49 | If your live reaches this limit, it will be automatically terminated. |
50 | </div> | 50 | </div> |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html index 14a7a287a..aa34d644a 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.html | |||
@@ -52,7 +52,7 @@ | |||
52 | {{ error }} | 52 | {{ error }} |
53 | </div> | 53 | </div> |
54 | 54 | ||
55 | <div *ngIf="hasImportedVideo && !error" class="alert alert-info" i18n> | 55 | <div *ngIf="hasImportedVideo && !error" class="alert pt-alert-primary" i18n> |
56 | Congratulations, the video will be imported with BitTorrent! You can already add information about this video. | 56 | Congratulations, the video will be imported with BitTorrent! You can already add information about this video. |
57 | </div> | 57 | </div> |
58 | 58 | ||
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html index 60a43c870..67e1cb418 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.html | |||
@@ -45,7 +45,7 @@ | |||
45 | {{ error }} | 45 | {{ error }} |
46 | </div> | 46 | </div> |
47 | 47 | ||
48 | <div *ngIf="!error && hasImportedVideo" class="alert alert-info" i18n> | 48 | <div *ngIf="!error && hasImportedVideo" class="alert pt-alert-primary" i18n> |
49 | Congratulations, the video behind {{ targetUrl }} will be imported! You can already add information about this video. | 49 | Congratulations, the video behind {{ targetUrl }} will be imported! You can already add information about this video. |
50 | </div> | 50 | </div> |
51 | 51 | ||
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts index 0c78669c1..4c74eda84 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts | |||
@@ -78,7 +78,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV | |||
78 | .pipe( | 78 | .pipe( |
79 | switchMap(res => { | 79 | switchMap(res => { |
80 | return this.videoCaptionService | 80 | return this.videoCaptionService |
81 | .listCaptions(res.video.id) | 81 | .listCaptions(res.video.uuid) |
82 | .pipe( | 82 | .pipe( |
83 | map(result => ({ video: res.video, videoCaptions: result.data })) | 83 | map(result => ({ video: res.video, videoCaptions: result.data })) |
84 | ) | 84 | ) |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html index 860fb76fa..728884986 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html | |||
@@ -87,7 +87,7 @@ | |||
87 | {{ error }} | 87 | {{ error }} |
88 | </div> | 88 | </div> |
89 | 89 | ||
90 | <div *ngIf="videoUploaded && !error" class="alert alert-info" i18n> | 90 | <div *ngIf="videoUploaded && !error" class="alert pt-alert-primary" i18n> |
91 | Congratulations! Your video is now available in your private library. | 91 | Congratulations! Your video is now available in your private library. |
92 | </div> | 92 | </div> |
93 | 93 | ||
diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html index 29cf08e75..27ad14d63 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.html +++ b/client/src/app/+videos/+video-edit/video-add.component.html | |||
@@ -40,7 +40,7 @@ | |||
40 | <div *ngIf="!user.isUploadDisabled()" class="margin-content"> | 40 | <div *ngIf="!user.isUploadDisabled()" class="margin-content"> |
41 | <my-user-quota *ngIf="!isInSecondStep() || secondStepType === 'go-live'" [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota> | 41 | <my-user-quota *ngIf="!isInSecondStep() || secondStepType === 'go-live'" [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota> |
42 | 42 | ||
43 | <div class="title-page title-page-single" *ngIf="isInSecondStep()"> | 43 | <div class="title-page" *ngIf="isInSecondStep()"> |
44 | <ng-container *ngIf="secondStepType === 'import-url' || secondStepType === 'import-torrent'" i18n>Import {{ videoName }}</ng-container> | 44 | <ng-container *ngIf="secondStepType === 'import-url' || secondStepType === 'import-torrent'" i18n>Import {{ videoName }}</ng-container> |
45 | <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container> | 45 | <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container> |
46 | </div> | 46 | </div> |
diff --git a/client/src/app/+videos/+video-edit/video-add.component.scss b/client/src/app/+videos/+video-edit/video-add.component.scss index dda868789..461a38204 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.scss +++ b/client/src/app/+videos/+video-edit/video-add.component.scss | |||
@@ -10,7 +10,6 @@ $nav-link-height: 40px; | |||
10 | .upload-message { | 10 | .upload-message { |
11 | width: 100%; | 11 | width: 100%; |
12 | text-align: center; | 12 | text-align: center; |
13 | font-size: 15px; | ||
14 | margin-bottom: 0; | 13 | margin-bottom: 0; |
15 | border-radius: 0; | 14 | border-radius: 0; |
16 | 15 | ||
diff --git a/client/src/app/+videos/+video-edit/video-update.component.html b/client/src/app/+videos/+video-edit/video-update.component.html index 4376f6fe4..ffd125695 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.html +++ b/client/src/app/+videos/+video-edit/video-update.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="margin-content"> | 1 | <div class="margin-content"> |
2 | <div class="title-page title-page-single"> | 2 | <div class="title-page"> |
3 | <span class="mr-1" i18n>Update</span> | 3 | <span class="me-1" i18n>Update</span> |
4 | <a [routerLink]="getVideoUrl()">{{ video?.name }}</a> | 4 | <a [routerLink]="getVideoUrl()">{{ video?.name }}</a> |
5 | </div> | 5 | </div> |
6 | 6 | ||
@@ -12,6 +12,7 @@ | |||
12 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="isWaitTranscodingEnabled()" | 12 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="isWaitTranscodingEnabled()" |
13 | type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" | 13 | type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" |
14 | [liveVideo]="liveVideo" [videoToUpdate]="videoDetails" | 14 | [liveVideo]="liveVideo" [videoToUpdate]="videoDetails" |
15 | [videoSource]="videoSource" | ||
15 | 16 | ||
16 | (formBuilt)="onFormBuilt()" | 17 | (formBuilt)="onFormBuilt()" |
17 | ></my-video-edit> | 18 | ></my-video-edit> |
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts index 9c4998f2e..43e8ba3e5 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.ts +++ b/client/src/app/+videos/+video-edit/video-update.component.ts | |||
@@ -10,6 +10,7 @@ import { LiveVideoService } from '@app/shared/shared-video-live' | |||
10 | import { LoadingBarService } from '@ngx-loading-bar/core' | 10 | import { LoadingBarService } from '@ngx-loading-bar/core' |
11 | import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models' | 11 | import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models' |
12 | import { hydrateFormFromVideo } from './shared/video-edit-utils' | 12 | import { hydrateFormFromVideo } from './shared/video-edit-utils' |
13 | import { VideoSource } from '@shared/models/videos/video-source' | ||
13 | 14 | ||
14 | @Component({ | 15 | @Component({ |
15 | selector: 'my-videos-update', | 16 | selector: 'my-videos-update', |
@@ -19,6 +20,7 @@ import { hydrateFormFromVideo } from './shared/video-edit-utils' | |||
19 | export class VideoUpdateComponent extends FormReactive implements OnInit { | 20 | export class VideoUpdateComponent extends FormReactive implements OnInit { |
20 | video: VideoEdit | 21 | video: VideoEdit |
21 | videoDetails: VideoDetails | 22 | videoDetails: VideoDetails |
23 | videoSource: VideoSource | ||
22 | userVideoChannels: SelectChannelItem[] = [] | 24 | userVideoChannels: SelectChannelItem[] = [] |
23 | videoCaptions: VideoCaptionEdit[] = [] | 25 | videoCaptions: VideoCaptionEdit[] = [] |
24 | liveVideo: LiveVideo | 26 | liveVideo: LiveVideo |
@@ -46,13 +48,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
46 | this.buildForm({}) | 48 | this.buildForm({}) |
47 | 49 | ||
48 | const { videoData } = this.route.snapshot.data | 50 | const { videoData } = this.route.snapshot.data |
49 | const { video, videoChannels, videoCaptions, liveVideo } = videoData | 51 | const { video, videoChannels, videoCaptions, videoSource, liveVideo } = videoData |
50 | 52 | ||
51 | this.video = new VideoEdit(video) | 53 | this.video = new VideoEdit(video) |
52 | this.videoDetails = video | 54 | this.videoDetails = video |
53 | 55 | ||
54 | this.userVideoChannels = videoChannels | 56 | this.userVideoChannels = videoChannels |
55 | this.videoCaptions = videoCaptions | 57 | this.videoCaptions = videoCaptions |
58 | this.videoSource = videoSource | ||
56 | this.liveVideo = liveVideo | 59 | this.liveVideo = liveVideo |
57 | 60 | ||
58 | this.forbidScheduledPublication = this.video.privacy !== VideoPrivacy.PRIVATE | 61 | this.forbidScheduledPublication = this.video.privacy !== VideoPrivacy.PRIVATE |
diff --git a/client/src/app/+videos/+video-edit/video-update.resolver.ts b/client/src/app/+videos/+video-edit/video-update.resolver.ts index 82dae5c1c..524ceae10 100644 --- a/client/src/app/+videos/+video-edit/video-update.resolver.ts +++ b/client/src/app/+videos/+video-edit/video-update.resolver.ts | |||
@@ -23,7 +23,8 @@ export class VideoUpdateResolver implements Resolve<any> { | |||
23 | return this.videoService.getVideo({ videoId: uuid }) | 23 | return this.videoService.getVideo({ videoId: uuid }) |
24 | .pipe( | 24 | .pipe( |
25 | switchMap(video => forkJoin(this.buildVideoObservables(video))), | 25 | switchMap(video => forkJoin(this.buildVideoObservables(video))), |
26 | map(([ video, videoChannels, videoCaptions, liveVideo ]) => ({ video, videoChannels, videoCaptions, liveVideo })) | 26 | map(([ video, videoSource, videoChannels, videoCaptions, liveVideo ]) => |
27 | ({ video, videoChannels, videoCaptions, videoSource, liveVideo })) | ||
27 | ) | 28 | ) |
28 | } | 29 | } |
29 | 30 | ||
@@ -33,10 +34,12 @@ export class VideoUpdateResolver implements Resolve<any> { | |||
33 | .loadCompleteDescription(video.descriptionPath) | 34 | .loadCompleteDescription(video.descriptionPath) |
34 | .pipe(map(description => Object.assign(video, { description }))), | 35 | .pipe(map(description => Object.assign(video, { description }))), |
35 | 36 | ||
37 | this.videoService.getSource(video.id), | ||
38 | |||
36 | listUserChannelsForSelect(this.authService), | 39 | listUserChannelsForSelect(this.authService), |
37 | 40 | ||
38 | this.videoCaptionService | 41 | this.videoCaptionService |
39 | .listCaptions(video.id) | 42 | .listCaptions(video.uuid) |
40 | .pipe( | 43 | .pipe( |
41 | map(result => result.data) | 44 | map(result => result.data) |
42 | ), | 45 | ), |
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html index f23efca98..cf32e371a 100644 --- a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html +++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div class="video-actions-rates"> | 1 | <div class="video-actions-rates"> |
2 | <div class="video-actions full-width justify-content-end"> | 2 | <div class="video-actions justify-content-end"> |
3 | <my-video-rate | 3 | <my-video-rate |
4 | [video]="video" [isUserLoggedIn]="isUserLoggedIn" | 4 | [video]="video" [isUserLoggedIn]="isUserLoggedIn" |
5 | (rateUpdated)="onRateUpdated($event)" (userRatingLoaded)="onRateUpdated($event)" | 5 | (rateUpdated)="onRateUpdated($event)" (userRatingLoaded)="onRateUpdated($event)" |
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss index fdf4e3edb..786d10f73 100644 --- a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss +++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss | |||
@@ -5,6 +5,9 @@ | |||
5 | height: 40px; // Align with the title | 5 | height: 40px; // Align with the title |
6 | display: flex; | 6 | display: flex; |
7 | align-items: center; | 7 | align-items: center; |
8 | width: 100%; | ||
9 | margin: 0 auto; | ||
10 | max-width: initial; | ||
8 | 11 | ||
9 | .action-button:not(:first-child), | 12 | .action-button:not(:first-child), |
10 | .action-dropdown, | 13 | .action-dropdown, |
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts index 48d48f33f..0fef246b3 100644 --- a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts +++ b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts | |||
@@ -89,7 +89,7 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy { | |||
89 | // Unlogged users do not have ratings | 89 | // Unlogged users do not have ratings |
90 | if (this.isUserLoggedIn === false) return | 90 | if (this.isUserLoggedIn === false) return |
91 | 91 | ||
92 | this.videoService.getUserVideoRating(this.video.id) | 92 | this.videoService.getUserVideoRating(this.video.uuid) |
93 | .subscribe({ | 93 | .subscribe({ |
94 | next: ratingObject => { | 94 | next: ratingObject => { |
95 | if (!ratingObject) return | 95 | if (!ratingObject) return |
@@ -103,13 +103,13 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy { | |||
103 | } | 103 | } |
104 | 104 | ||
105 | private setRating (nextRating: UserVideoRateType) { | 105 | private setRating (nextRating: UserVideoRateType) { |
106 | const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = { | 106 | const ratingMethods: { [id in UserVideoRateType]: (id: string) => Observable<any> } = { |
107 | like: this.videoService.setVideoLike, | 107 | like: this.videoService.setVideoLike, |
108 | dislike: this.videoService.setVideoDislike, | 108 | dislike: this.videoService.setVideoDislike, |
109 | none: this.videoService.unsetVideoLike | 109 | none: this.videoService.unsetVideoLike |
110 | } | 110 | } |
111 | 111 | ||
112 | ratingMethods[nextRating].call(this.videoService, this.video.id) | 112 | ratingMethods[nextRating].call(this.videoService, this.video.uuid) |
113 | .subscribe({ | 113 | .subscribe({ |
114 | next: () => { | 114 | next: () => { |
115 | // Update the video like attribute | 115 | // Update the video like attribute |
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.html index 3ee818c8b..6bc201f32 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.html +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.html | |||
@@ -1,8 +1,8 @@ | |||
1 | <form novalidate [formGroup]="form" (ngSubmit)="formValidated()"> | 1 | <form novalidate [formGroup]="form" (ngSubmit)="formValidated()"> |
2 | <div class="avatar-and-textarea"> | 2 | <div class="avatar-and-textarea"> |
3 | <my-actor-avatar [account]="user?.account" size="25"></my-actor-avatar> | 3 | <my-actor-avatar [actor]="user?.account" [actorType]="getAvatarActorType()" size="25"></my-actor-avatar> |
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="textarea-wrapper"> |
6 | <textarea i18n-placeholder placeholder="Add comment..." myAutoResize | 6 | <textarea i18n-placeholder placeholder="Add comment..." myAutoResize |
7 | [readonly]="(user === null) ? true : false" | 7 | [readonly]="(user === null) ? true : false" |
8 | (click)="openVisitorModal($event)" | 8 | (click)="openVisitorModal($event)" |
@@ -88,8 +88,8 @@ | |||
88 | </div> | 88 | </div> |
89 | <div class="modal-body"> | 89 | <div class="modal-body"> |
90 | <div class="emoji-flex"> | 90 | <div class="emoji-flex"> |
91 | <div class="emoji-flex-item" *ngFor="let emojiMarkup of emojiMarkupList"> | 91 | <div class="emoji-flex-item" *ngFor="let emojiMarkup of getEmojiMarkupList()"> |
92 | {{ emojiMarkup[0] }} <code>:{{ emojiMarkup[1] }}:</code> | 92 | {{ emojiMarkup.emoji }} <code>:{{ emojiMarkup.name }}:</code> |
93 | </div> | 93 | </div> |
94 | </div> | 94 | </div> |
95 | </div> | 95 | </div> |
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.scss b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.scss index ae889dd38..023d625e9 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.scss +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.scss | |||
@@ -17,9 +17,8 @@ form { | |||
17 | @include margin-right(10px); | 17 | @include margin-right(10px); |
18 | } | 18 | } |
19 | 19 | ||
20 | .form-group { | 20 | .textarea-wrapper { |
21 | flex-grow: 1; | 21 | flex-grow: 1; |
22 | margin: 0; | ||
23 | position: relative; | 22 | position: relative; |
24 | } | 23 | } |
25 | 24 | ||
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts index 85da83a4c..fd3614297 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts | |||
@@ -45,6 +45,8 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, | |||
45 | addingComment = false | 45 | addingComment = false |
46 | addingCommentButtonValue: string | 46 | addingCommentButtonValue: string |
47 | 47 | ||
48 | private emojiMarkupList: { emoji: string, name: string }[] | ||
49 | |||
48 | constructor ( | 50 | constructor ( |
49 | protected formValidatorService: FormValidatorService, | 51 | protected formValidatorService: FormValidatorService, |
50 | private notifier: Notifier, | 52 | private notifier: Notifier, |
@@ -56,21 +58,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, | |||
56 | super() | 58 | super() |
57 | } | 59 | } |
58 | 60 | ||
59 | get emojiMarkupList () { | ||
60 | const emojiMarkupObjectList = require('markdown-it-emoji/lib/data/light.json') | ||
61 | |||
62 | // Populate emoji-markup-list from object to array to avoid keys alphabetical order | ||
63 | const emojiMarkupArrayList = [] | ||
64 | for (const emojiMarkupName in emojiMarkupObjectList) { | ||
65 | if (emojiMarkupName) { | ||
66 | const emoji = emojiMarkupObjectList[emojiMarkupName] | ||
67 | emojiMarkupArrayList.push([ emoji, emojiMarkupName ]) | ||
68 | } | ||
69 | } | ||
70 | |||
71 | return emojiMarkupArrayList | ||
72 | } | ||
73 | |||
74 | ngOnInit () { | 61 | ngOnInit () { |
75 | this.buildForm({ | 62 | this.buildForm({ |
76 | text: VIDEO_COMMENT_TEXT_VALIDATOR | 63 | text: VIDEO_COMMENT_TEXT_VALIDATOR |
@@ -96,6 +83,20 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, | |||
96 | } | 83 | } |
97 | } | 84 | } |
98 | 85 | ||
86 | getEmojiMarkupList () { | ||
87 | if (this.emojiMarkupList) return this.emojiMarkupList | ||
88 | |||
89 | const emojiMarkupObjectList = require('markdown-it-emoji/lib/data/light.json') | ||
90 | |||
91 | this.emojiMarkupList = [] | ||
92 | for (const name of Object.keys(emojiMarkupObjectList)) { | ||
93 | const emoji = emojiMarkupObjectList[name] | ||
94 | this.emojiMarkupList.push({ emoji, name }) | ||
95 | } | ||
96 | |||
97 | return this.emojiMarkupList | ||
98 | } | ||
99 | |||
99 | onValidKey () { | 100 | onValidKey () { |
100 | this.forceCheck() | 101 | this.forceCheck() |
101 | if (!this.form.valid) return | 102 | if (!this.form.valid) return |
@@ -174,14 +175,20 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, | |||
174 | return getLocaleDirection(this.localeId) === 'rtl' | 175 | return getLocaleDirection(this.localeId) === 'rtl' |
175 | } | 176 | } |
176 | 177 | ||
178 | getAvatarActorType () { | ||
179 | if (this.user) return 'account' | ||
180 | |||
181 | return 'unlogged' | ||
182 | } | ||
183 | |||
177 | private addCommentReply (commentCreate: VideoCommentCreate) { | 184 | private addCommentReply (commentCreate: VideoCommentCreate) { |
178 | return this.videoCommentService | 185 | return this.videoCommentService |
179 | .addCommentReply(this.video.id, this.parentComment.id, commentCreate) | 186 | .addCommentReply(this.video.uuid, this.parentComment.id, commentCreate) |
180 | } | 187 | } |
181 | 188 | ||
182 | private addCommentThread (commentCreate: VideoCommentCreate) { | 189 | private addCommentThread (commentCreate: VideoCommentCreate) { |
183 | return this.videoCommentService | 190 | return this.videoCommentService |
184 | .addCommentThread(this.video.id, commentCreate) | 191 | .addCommentThread(this.video.uuid, commentCreate) |
185 | } | 192 | } |
186 | 193 | ||
187 | private initTextValue () { | 194 | private initTextValue () { |
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html index 5014b9692..da35a9a2e 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html | |||
@@ -1,6 +1,10 @@ | |||
1 | <div *ngIf="isCommentDisplayed()" class="root-comment" [ngClass]="{ 'is-child': isChild() }"> | 1 | <div *ngIf="isCommentDisplayed()" class="root-comment" [ngClass]="{ 'is-child': isChild() }"> |
2 | <div class="left"> | 2 | <div class="left"> |
3 | <my-actor-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account" [size]="isChild() ? '25' : '36'"></my-actor-avatar> | 3 | <my-actor-avatar |
4 | *ngIf="!comment.isDeleted" [href]="comment.account.url" | ||
5 | [actor]="comment.account" actorType="account" [size]="isChild() ? '25' : '36'" | ||
6 | ></my-actor-avatar> | ||
7 | |||
4 | <div class="vertical-border"></div> | 8 | <div class="vertical-border"></div> |
5 | </div> | 9 | </div> |
6 | 10 | ||
@@ -16,7 +20,7 @@ | |||
16 | {{ comment.account.displayName }} | 20 | {{ comment.account.displayName }} |
17 | </span> | 21 | </span> |
18 | 22 | ||
19 | <span class="comment-account-fid ml-1">{{ comment.by }}</span> | 23 | <span class="comment-account-fid ms-1">{{ comment.by }}</span> |
20 | </a> | 24 | </a> |
21 | </div> | 25 | </div> |
22 | 26 | ||
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss index 54f828014..8b5034083 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss | |||
@@ -2,7 +2,6 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .root-comment { | 4 | .root-comment { |
5 | font-size: 15px; | ||
6 | display: flex; | 5 | display: flex; |
7 | 6 | ||
8 | .left { | 7 | .left { |
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html index 0e00c9c0e..e27942e66 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html | |||
@@ -1,12 +1,12 @@ | |||
1 | <div> | 1 | <div> |
2 | <div class="title-block"> | 2 | <div class="title-block"> |
3 | <h2 class="title-page title-page-single"> | 3 | <h2 class="title-page"> |
4 | {totalNotDeletedComments, plural, =0 {Comments} =1 {1 Comment} other {{{totalNotDeletedComments}} Comments}} | 4 | {totalNotDeletedComments, plural, =0 {Comments} =1 {1 Comment} other {{{totalNotDeletedComments}} Comments}} |
5 | </h2> | 5 | </h2> |
6 | 6 | ||
7 | <my-feed [syndicationItems]="syndicationItems"></my-feed> | 7 | <my-feed [syndicationItems]="syndicationItems"></my-feed> |
8 | 8 | ||
9 | <div ngbDropdown class="d-inline-block ml-4 dropdown-root"> | 9 | <div ngbDropdown class="d-inline-block ms-4 dropdown-root"> |
10 | <button class="btn btn-sm btn-outline-secondary" id="dropdown-sort-comments" ngbDropdownToggle i18n> | 10 | <button class="btn btn-sm btn-outline-secondary" id="dropdown-sort-comments" ngbDropdownToggle i18n> |
11 | SORT BY | 11 | SORT BY |
12 | </button> | 12 | </button> |
@@ -65,7 +65,7 @@ | |||
65 | [redraftValue]="commentReplyRedraftValue" | 65 | [redraftValue]="commentReplyRedraftValue" |
66 | > | 66 | > |
67 | <div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment.id)" class="view-replies mb-2"> | 67 | <div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment.id)" class="view-replies mb-2"> |
68 | <span class="glyphicon glyphicon-menu-down"></span> | 68 | <span class="chevron-down"></span> |
69 | 69 | ||
70 | <ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container> | 70 | <ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container> |
71 | 71 | ||
@@ -80,7 +80,7 @@ | |||
80 | 80 | ||
81 | <ng-template i18n #noAuthorComments>View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}}</ng-template> | 81 | <ng-template i18n #noAuthorComments>View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}}</ng-template> |
82 | 82 | ||
83 | <my-small-loader class="comment-thread-loading ml-1" [loading]="threadLoading[comment.id]"></my-small-loader> | 83 | <my-loader size="sm" class="ms-1" [loading]="threadLoading[comment.id]"></my-loader> |
84 | </div> | 84 | </div> |
85 | </my-video-comment> | 85 | </my-video-comment> |
86 | 86 | ||
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.scss b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.scss index 31aa73937..638147dfe 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.scss +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.scss | |||
@@ -7,18 +7,9 @@ | |||
7 | 7 | ||
8 | .view-replies { | 8 | .view-replies { |
9 | font-weight: $font-semibold; | 9 | font-weight: $font-semibold; |
10 | font-size: 15px; | ||
11 | cursor: pointer; | 10 | cursor: pointer; |
12 | } | 11 | } |
13 | 12 | ||
14 | .glyphicon, | ||
15 | .comment-thread-loading { | ||
16 | @include margin-right(5px); | ||
17 | |||
18 | display: inline-block; | ||
19 | font-size: 13px; | ||
20 | } | ||
21 | |||
22 | .title-block { | 13 | .title-block { |
23 | .title-page { | 14 | .title-page { |
24 | @include margin-right(0); | 15 | @include margin-right(0); |
@@ -41,10 +32,9 @@ | |||
41 | } | 32 | } |
42 | 33 | ||
43 | #dropdown-sort-comments { | 34 | #dropdown-sort-comments { |
44 | font-weight: 600; | 35 | font-weight: $font-semibold; |
45 | text-transform: uppercase; | 36 | text-transform: uppercase; |
46 | border: 0; | 37 | border: 0; |
47 | transform: translateY(-7%); | ||
48 | } | 38 | } |
49 | 39 | ||
50 | @media screen and (max-width: 600px) { | 40 | @media screen and (max-width: 600px) { |
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts index 17e0af3bc..8e556c58f 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts | |||
@@ -78,7 +78,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
78 | this.threadLoading[commentId] = true | 78 | this.threadLoading[commentId] = true |
79 | 79 | ||
80 | const params = { | 80 | const params = { |
81 | videoId: this.video.id, | 81 | videoId: this.video.uuid, |
82 | threadId: commentId | 82 | threadId: commentId |
83 | } | 83 | } |
84 | 84 | ||
@@ -110,7 +110,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
110 | 110 | ||
111 | loadMoreThreads () { | 111 | loadMoreThreads () { |
112 | const params = { | 112 | const params = { |
113 | videoId: this.video.id, | 113 | videoId: this.video.uuid, |
114 | componentPagination: this.componentPagination, | 114 | componentPagination: this.componentPagination, |
115 | sort: this.sort | 115 | sort: this.sort |
116 | } | 116 | } |
diff --git a/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.html b/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.html index d579aaddb..b64d45564 100644 --- a/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.html +++ b/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="privacy-concerns" *ngIf="display"> | 1 | <div class="privacy-concerns" *ngIf="display"> |
2 | <div class="privacy-concerns-text"> | 2 | <div class="privacy-concerns-text"> |
3 | <span class="mr-2"> | 3 | <span class="me-2"> |
4 | <strong i18n>Friendly Reminder: </strong> | 4 | <strong i18n>Friendly Reminder: </strong> |
5 | <ng-container i18n> | 5 | <ng-container i18n> |
6 | the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers. | 6 | the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers. |
diff --git a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html index be726c990..79b83811d 100644 --- a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html +++ b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html | |||
@@ -22,15 +22,15 @@ | |||
22 | The video is being moved to an external server, it may not work properly. | 22 | The video is being moved to an external server, it may not work properly. |
23 | </div> | 23 | </div> |
24 | 24 | ||
25 | <div i18n class="alert alert-info" *ngIf="hasVideoScheduledPublication()"> | 25 | <div i18n class="alert pt-alert-primary" *ngIf="hasVideoScheduledPublication()"> |
26 | This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}. | 26 | This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}. |
27 | </div> | 27 | </div> |
28 | 28 | ||
29 | <div i18n class="alert alert-info" *ngIf="isWaitingForLive()"> | 29 | <div i18n class="alert pt-alert-primary" *ngIf="isWaitingForLive()"> |
30 | This live has not started yet. | 30 | This live has not started yet. |
31 | </div> | 31 | </div> |
32 | 32 | ||
33 | <div i18n class="alert alert-info" *ngIf="isLiveEnded()"> | 33 | <div i18n class="alert pt-alert-primary" *ngIf="isLiveEnded()"> |
34 | This live has ended. | 34 | This live has ended. |
35 | </div> | 35 | </div> |
36 | 36 | ||
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html index 10ff46595..52ad1999d 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html | |||
@@ -11,9 +11,11 @@ | |||
11 | >{{ video.originInstanceHost }}</a> | 11 | >{{ video.originInstanceHost }}</a> |
12 | 12 | ||
13 | <a | 13 | <a |
14 | i18n-title title="Open the video on the origin instance" class="glyphicon glyphicon-new-window" | 14 | i18n-title title="Open the video on the origin instance" |
15 | target="_blank" rel="noopener noreferrer" [href]="video.url" | 15 | target="_blank" rel="noopener noreferrer" [href]="video.url" |
16 | ></a> | 16 | > |
17 | <my-global-icon iconName="external-link"></my-global-icon> | ||
18 | </a> | ||
17 | </div> | 19 | </div> |
18 | 20 | ||
19 | <div *ngIf="!!video.originallyPublishedAt" class="attribute attribute-originally-published-at"> | 21 | <div *ngIf="!!video.originallyPublishedAt" class="attribute attribute-originally-published-at"> |
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss index 26bead124..1470a9f6d 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.scss | |||
@@ -33,12 +33,6 @@ a.attribute-value { | |||
33 | } | 33 | } |
34 | } | 34 | } |
35 | 35 | ||
36 | .glyphicon-new-window { | ||
37 | color: pvar(--inputPlaceholderColor); | ||
38 | margin-left: 5px; | ||
39 | font-size: 12px; | ||
40 | } | ||
41 | |||
42 | @media screen and (max-width: 1600px) { | 36 | @media screen and (max-width: 1600px) { |
43 | .attributes .attribute { | 37 | .attributes .attribute { |
44 | margin-bottom: 5px; | 38 | margin-bottom: 5px; |
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html index a23152b67..a608a22f6 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <my-actor-avatar | 2 | <my-actor-avatar |
3 | *ngIf="showChannel" | 3 | *ngIf="showChannel" |
4 | class="channel" | 4 | class="channel" |
5 | [channel]="video.channel" | 5 | [actor]="video.channel" actorType="channel" |
6 | [internalHref]="[ '/c', video.byVideoChannel ]" | 6 | [internalHref]="[ '/c', video.byVideoChannel ]" |
7 | [title]="channelLinkTitle" | 7 | [title]="channelLinkTitle" |
8 | size="35" | 8 | size="35" |
@@ -12,7 +12,7 @@ | |||
12 | *ngIf="showAccount" | 12 | *ngIf="showAccount" |
13 | class="account" | 13 | class="account" |
14 | [class.second-avatar]="showChannel" | 14 | [class.second-avatar]="showChannel" |
15 | [account]="video.account" | 15 | [actor]="video.account" actorType="account" |
16 | [internalHref]="[ '/a', video.byAccount ]" | 16 | [internalHref]="[ '/a', video.byAccount ]" |
17 | [title]="accountLinkTitle" | 17 | [title]="accountLinkTitle" |
18 | size="35"> | 18 | size="35"> |
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss index 80711ff32..fd9dd1a6a 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss | |||
@@ -1,14 +1,5 @@ | |||
1 | @use '_mixins' as *; | 1 | @use '_mixins' as *; |
2 | 2 | ||
3 | @mixin secondary { | ||
4 | height: 60%; | ||
5 | width: 60%; | ||
6 | position: absolute; | ||
7 | bottom: -5px; | ||
8 | right: -5px; | ||
9 | background-color: rgba(0, 0, 0, 0); | ||
10 | } | ||
11 | |||
12 | .wrapper { | 3 | .wrapper { |
13 | @include margin-right(5px); | 4 | @include margin-right(5px); |
14 | 5 | ||
@@ -16,6 +7,11 @@ | |||
16 | margin-bottom: 5px; | 7 | margin-bottom: 5px; |
17 | 8 | ||
18 | .second-avatar { | 9 | .second-avatar { |
19 | @include secondary(); | 10 | height: 60%; |
11 | width: 60%; | ||
12 | position: absolute; | ||
13 | bottom: -5px; | ||
14 | right: -5px; | ||
15 | background-color: rgba(0, 0, 0, 0); | ||
20 | } | 16 | } |
21 | } | 17 | } |
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html index 2cfaad8f6..fa4dbb3ca 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html | |||
@@ -8,12 +8,12 @@ | |||
8 | 8 | ||
9 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> | 9 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> |
10 | <ng-container i18n>Show more</ng-container> | 10 | <ng-container i18n>Show more</ng-container> |
11 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> | 11 | <span *ngIf="descriptionLoading === false" class="chevron-down"></span> |
12 | <my-small-loader class="description-loading" [loading]="descriptionLoading"></my-small-loader> | 12 | <my-loader size="sm" class="description-loading" [loading]="descriptionLoading"></my-loader> |
13 | </div> | 13 | </div> |
14 | 14 | ||
15 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> | 15 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> |
16 | <ng-container i18n>Show less</ng-container> | 16 | <ng-container i18n>Show less</ng-container> |
17 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span> | 17 | <span *ngIf="descriptionLoading === false" class="chevron-up"></span> |
18 | </div> | 18 | </div> |
19 | </div> | 19 | </div> |
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss index fc8b4574c..b503a94cb 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.scss | |||
@@ -7,7 +7,6 @@ | |||
7 | 7 | ||
8 | margin-top: 20px; | 8 | margin-top: 20px; |
9 | margin-bottom: 20px; | 9 | margin-bottom: 20px; |
10 | font-size: 15px; | ||
11 | 10 | ||
12 | .video-info-description-html { | 11 | .video-info-description-html { |
13 | @include peertube-word-wrap; | 12 | @include peertube-word-wrap; |
@@ -17,13 +16,8 @@ | |||
17 | } | 16 | } |
18 | } | 17 | } |
19 | 18 | ||
20 | .glyphicon, | ||
21 | .description-loading { | 19 | .description-loading { |
22 | @include margin-left(3px); | 20 | @include margin-left(5px); |
23 | } | ||
24 | |||
25 | .description-loading { | ||
26 | display: inline-block; | ||
27 | } | 21 | } |
28 | 22 | ||
29 | .video-info-description-more { | 23 | .video-info-description-more { |
@@ -31,11 +25,6 @@ | |||
31 | font-weight: $font-semibold; | 25 | font-weight: $font-semibold; |
32 | color: pvar(--greyForegroundColor); | 26 | color: pvar(--greyForegroundColor); |
33 | font-size: 14px; | 27 | font-size: 14px; |
34 | |||
35 | .glyphicon { | ||
36 | position: relative; | ||
37 | top: 2px; | ||
38 | } | ||
39 | } | 28 | } |
40 | } | 29 | } |
41 | 30 | ||
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html index f5dd352a3..b04bd3548 100644 --- a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html +++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html | |||
@@ -6,9 +6,9 @@ | |||
6 | <div class="playlist-display-name"> | 6 | <div class="playlist-display-name"> |
7 | {{ playlist.displayName }} | 7 | {{ playlist.displayName }} |
8 | 8 | ||
9 | <span *ngIf="isUnlistedPlaylist()" class="badge badge-warning" i18n>Unlisted</span> | 9 | <span *ngIf="isUnlistedPlaylist()" class="pt-badge badge-warning" i18n>Unlisted</span> |
10 | <span *ngIf="isPrivatePlaylist()" class="badge badge-danger" i18n>Private</span> | 10 | <span *ngIf="isPrivatePlaylist()" class="pt-badge badge-danger" i18n>Private</span> |
11 | <span *ngIf="isPublicPlaylist()" class="badge badge-info" i18n>Public</span> | 11 | <span *ngIf="isPublicPlaylist()" class="pt-badge badge-info" i18n>Public</span> |
12 | </div> | 12 | </div> |
13 | 13 | ||
14 | <div class="playlist-by-index"> | 14 | <div class="playlist-by-index"> |
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss index 5c3453e4b..0f0ac1979 100644 --- a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss +++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss | |||
@@ -15,43 +15,47 @@ | |||
15 | .playlist-info { | 15 | .playlist-info { |
16 | padding: 5px 30px; | 16 | padding: 5px 30px; |
17 | background-color: pvar(--greyBackgroundColor); | 17 | background-color: pvar(--greyBackgroundColor); |
18 | } | ||
19 | |||
20 | .playlist-display-name { | ||
21 | font-size: 18px; | ||
22 | font-weight: $font-semibold; | ||
23 | margin-bottom: 5px; | ||
18 | 24 | ||
19 | .playlist-display-name { | 25 | .pt-badge { |
20 | font-size: 18px; | 26 | @include margin-left(5px); |
21 | font-weight: $font-semibold; | ||
22 | margin-bottom: 5px; | ||
23 | } | 27 | } |
28 | } | ||
24 | 29 | ||
25 | .playlist-by-index { | 30 | .playlist-by-index { |
26 | color: pvar(--greyForegroundColor); | 31 | color: pvar(--greyForegroundColor); |
27 | display: flex; | 32 | display: flex; |
28 | 33 | ||
29 | .playlist-by { | 34 | .playlist-by { |
30 | @include margin-right(5px); | 35 | @include margin-right(5px); |
31 | } | 36 | } |
32 | 37 | ||
33 | .playlist-index span:first-child::after { | 38 | .playlist-index span:first-child::after { |
34 | content: '/'; | 39 | content: '/'; |
35 | margin: 0 3px; | 40 | margin: 0 3px; |
36 | } | ||
37 | } | 41 | } |
42 | } | ||
38 | 43 | ||
39 | .playlist-controls { | 44 | .playlist-controls { |
40 | display: flex; | 45 | display: flex; |
41 | margin: 10px 0; | 46 | margin: 10px 0; |
42 | 47 | ||
43 | my-global-icon:not(:last-child) { | 48 | my-global-icon:not(:last-child) { |
44 | @include margin-right(.5rem); | 49 | @include margin-right(.5rem); |
45 | } | 50 | } |
46 | 51 | ||
47 | my-global-icon { | 52 | my-global-icon { |
48 | &:not(.active) { | 53 | &:not(.active) { |
49 | opacity: .5; | 54 | opacity: .5; |
50 | } | 55 | } |
51 | 56 | ||
52 | ::ng-deep { | 57 | ::ng-deep { |
53 | cursor: pointer; | 58 | cursor: pointer; |
54 | } | ||
55 | } | 59 | } |
56 | } | 60 | } |
57 | } | 61 | } |
diff --git a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html index e493ad8d7..5ac801622 100644 --- a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html +++ b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.html | |||
@@ -1,9 +1,8 @@ | |||
1 | <div class="other-videos" [ngClass]="{ 'display-as-row': displayAsRow }"> | 1 | <div class="other-videos" [ngClass]="{ 'display-as-row': displayAsRow }"> |
2 | <ng-container *ngIf="hasVideos$ | async"> | 2 | <ng-container *ngIf="hasVideos$ | async"> |
3 | <div class="title-page-container"> | 3 | <div class="title-page-container"> |
4 | <h2 i18n class="title-page title-page-single"> | 4 | <h2 i18n class="title-page">Other videos</h2> |
5 | Other videos | 5 | |
6 | </h2> | ||
7 | <div *ngIf="!playlist" class="title-page-autoplay" | 6 | <div *ngIf="!playlist" class="title-page-autoplay" |
8 | [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto" | 7 | [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto" |
9 | > | 8 | > |
diff --git a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.scss b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.scss index 784d0e961..5b1bf9cab 100644 --- a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.scss +++ b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.scss | |||
@@ -8,18 +8,14 @@ | |||
8 | margin-bottom: 25px; | 8 | margin-bottom: 25px; |
9 | flex-wrap: wrap-reverse; | 9 | flex-wrap: wrap-reverse; |
10 | 10 | ||
11 | .title-page.active, | 11 | .title-page { |
12 | .title-page.title-page-single { | ||
13 | @include margin-right(.5rem !important); | 12 | @include margin-right(.5rem !important); |
14 | 13 | ||
15 | margin-bottom: unset; | 14 | margin-bottom: unset; |
15 | margin-top: 0; | ||
16 | } | 16 | } |
17 | } | 17 | } |
18 | 18 | ||
19 | .title-page { | ||
20 | margin-top: 0; | ||
21 | } | ||
22 | |||
23 | .title-page-autoplay { | 19 | .title-page-autoplay { |
24 | @include margin-left(auto); | 20 | @include margin-left(auto); |
25 | 21 | ||
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.html b/client/src/app/+videos/+video-watch/video-watch.component.html index 1ea0cf6b8..461891779 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.html +++ b/client/src/app/+videos/+video-watch/video-watch.component.html | |||
@@ -61,7 +61,7 @@ | |||
61 | <div class="video-info-channel-left d-flex"> | 61 | <div class="video-info-channel-left d-flex"> |
62 | <my-video-avatar-channel [video]="video" [showChannel]="!isChannelDisplayNameGeneric()"></my-video-avatar-channel> | 62 | <my-video-avatar-channel [video]="video" [showChannel]="!isChannelDisplayNameGeneric()"></my-video-avatar-channel> |
63 | 63 | ||
64 | <div class="video-info-channel-left-links ml-1"> | 64 | <div class="video-info-channel-left-links ms-1"> |
65 | <ng-container *ngIf="!isChannelDisplayNameGeneric()"> | 65 | <ng-container *ngIf="!isChannelDisplayNameGeneric()"> |
66 | <a [routerLink]="[ '/c', video.byVideoChannel ]" i18n-title title="Channel page"> | 66 | <a [routerLink]="[ '/c', video.byVideoChannel ]" i18n-title title="Channel page"> |
67 | {{ video.channel.displayName }} | 67 | {{ video.channel.displayName }} |
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.scss b/client/src/app/+videos/+video-watch/video-watch.component.scss index 6818a4257..d438facd3 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.scss +++ b/client/src/app/+videos/+video-watch/video-watch.component.scss | |||
@@ -112,7 +112,6 @@ $video-height: 66vh; | |||
112 | margin-top: 50px; | 112 | margin-top: 50px; |
113 | text-align: center; | 113 | text-align: center; |
114 | font-weight: $font-semibold; | 114 | font-weight: $font-semibold; |
115 | font-size: 15px; | ||
116 | } | 115 | } |
117 | 116 | ||
118 | .video-bottom { | 117 | .video-bottom { |
@@ -158,12 +157,11 @@ $video-height: 66vh; | |||
158 | 157 | ||
159 | margin-bottom: 10px; | 158 | margin-bottom: 10px; |
160 | align-self: start; | 159 | align-self: start; |
161 | font-size: 1em; | 160 | font-size: 14px; |
162 | } | 161 | } |
163 | 162 | ||
164 | .video-info-channel { | 163 | .video-info-channel { |
165 | font-weight: $font-semibold; | 164 | font-weight: $font-semibold; |
166 | font-size: 15px; | ||
167 | 165 | ||
168 | a { | 166 | a { |
169 | @include disable-default-a-behaviour; | 167 | @include disable-default-a-behaviour; |
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.html b/client/src/app/+videos/video-list/overview/video-overview.component.html index f250c2407..b38516af8 100644 --- a/client/src/app/+videos/video-list/overview/video-overview.component.html +++ b/client/src/app/+videos/video-list/overview/video-overview.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <h1 class="sr-only" i18n>Discover</h1> | 1 | <h1 class="visually-hidden" i18n>Discover</h1> |
2 | <div class="margin-content"> | 2 | <div class="margin-content"> |
3 | 3 | ||
4 | <div class="no-results" i18n *ngIf="notResults">No results.</div> | 4 | <div class="no-results" i18n *ngIf="notResults">No results.</div> |
@@ -10,7 +10,7 @@ | |||
10 | 10 | ||
11 | <div class="section videos" *ngFor="let object of overview.categories"> | 11 | <div class="section videos" *ngFor="let object of overview.categories"> |
12 | <h1 class="section-title"> | 12 | <h1 class="section-title"> |
13 | <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a> | 13 | <a class="link-orange" routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a> |
14 | </h1> | 14 | </h1> |
15 | 15 | ||
16 | <div class="video-wrapper" *ngFor="let video of buildVideos(object.videos)"> | 16 | <div class="video-wrapper" *ngFor="let video of buildVideos(object.videos)"> |
@@ -21,7 +21,7 @@ | |||
21 | 21 | ||
22 | <div class="section videos" *ngFor="let object of overview.tags"> | 22 | <div class="section videos" *ngFor="let object of overview.tags"> |
23 | <h2 class="section-title"> | 23 | <h2 class="section-title"> |
24 | <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a> | 24 | <a class="link-orange" routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a> |
25 | </h2> | 25 | </h2> |
26 | 26 | ||
27 | <div class="video-wrapper" *ngFor="let video of buildVideos(object.videos)"> | 27 | <div class="video-wrapper" *ngFor="let video of buildVideos(object.videos)"> |
@@ -32,8 +32,8 @@ | |||
32 | 32 | ||
33 | <div class="section channel videos" *ngFor="let object of overview.channels"> | 33 | <div class="section channel videos" *ngFor="let object of overview.channels"> |
34 | <div class="section-title"> | 34 | <div class="section-title"> |
35 | <a [routerLink]="[ '/c', buildVideoChannelBy(object) ]"> | 35 | <a class="link-orange" [routerLink]="[ '/c', buildVideoChannelBy(object) ]"> |
36 | <my-actor-avatar [channel]="buildVideoChannel(object)" size="28"></my-actor-avatar> | 36 | <my-actor-avatar [actor]="buildVideoChannel(object)" actorType="channel" size="28"></my-actor-avatar> |
37 | 37 | ||
38 | <h2 class="section-title">{{ object.channel.displayName }}</h2> | 38 | <h2 class="section-title">{{ object.channel.displayName }}</h2> |
39 | </a> | 39 | </a> |
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.scss b/client/src/app/+videos/video-list/overview/video-overview.component.scss index 8b2aa88f2..5a789b66d 100644 --- a/client/src/app/+videos/video-list/overview/video-overview.component.scss +++ b/client/src/app/+videos/video-list/overview/video-overview.component.scss | |||
@@ -13,7 +13,7 @@ | |||
13 | 13 | ||
14 | .section { | 14 | .section { |
15 | &:first-child { | 15 | &:first-child { |
16 | padding-top: 30px; | 16 | padding-top: 15px; |
17 | 17 | ||
18 | .section-title { | 18 | .section-title { |
19 | border-top: 0 !important; | 19 | border-top: 0 !important; |
@@ -22,66 +22,27 @@ | |||
22 | 22 | ||
23 | .section-title { | 23 | .section-title { |
24 | font-size: 24px; | 24 | font-size: 24px; |
25 | font-weight: $font-semibold; | 25 | padding-top: 20px; |
26 | padding-top: 15px; | 26 | margin-bottom: 30px; |
27 | margin-bottom: 15px; | ||
28 | display: flex; | ||
29 | justify-content: space-between; | ||
30 | 27 | ||
31 | &:not(h2) { | 28 | &:not(h2) { |
32 | border-top: 1px solid $separator-border-color; | 29 | border-top: 1px solid $separator-border-color; |
33 | } | 30 | } |
34 | 31 | ||
35 | a { | 32 | a > h2 { |
36 | color: pvar(--mainForegroundColor); | 33 | margin-bottom: 0; |
37 | 34 | display: inline-block; | |
38 | &:hover, | 35 | font-weight: $font-bold; |
39 | &:focus:not(.focus-visible), | ||
40 | &:active { | ||
41 | text-decoration: none; | ||
42 | outline: none; | ||
43 | } | ||
44 | } | 36 | } |
45 | } | ||
46 | |||
47 | &.channel { | ||
48 | .section-title { | ||
49 | a { | ||
50 | display: flex; | ||
51 | width: fit-content; | ||
52 | align-items: center; | ||
53 | |||
54 | my-actor-avatar { | ||
55 | @include margin-right(8px); | ||
56 | |||
57 | font-size: initial; | ||
58 | } | ||
59 | } | ||
60 | 37 | ||
61 | .followers { | 38 | my-actor-avatar { |
62 | @include margin-left(10px); | 39 | @include margin-right(8px); |
63 | 40 | ||
64 | color: pvar(--greyForegroundColor); | 41 | position: relative; |
65 | font-weight: normal; | 42 | top: -2px; |
66 | font-size: 14px; | ||
67 | position: relative; | ||
68 | top: 2px; | ||
69 | } | ||
70 | } | 43 | } |
71 | } | 44 | } |
72 | 45 | ||
73 | .show-more { | ||
74 | position: relative; | ||
75 | top: -5px; | ||
76 | display: inline-block; | ||
77 | font-size: 16px; | ||
78 | text-transform: uppercase; | ||
79 | color: pvar(--greyForegroundColor); | ||
80 | margin-bottom: 10px; | ||
81 | font-weight: $font-semibold; | ||
82 | text-decoration: none; | ||
83 | } | ||
84 | |||
85 | @media screen and (max-width: $mobile-view) { | 46 | @media screen and (max-width: $mobile-view) { |
86 | max-height: initial; | 47 | max-height: initial; |
87 | overflow: initial; | 48 | overflow: initial; |
diff --git a/client/src/app/+videos/video-list/videos-list-common-page.component.ts b/client/src/app/+videos/video-list/videos-list-common-page.component.ts index d2782036b..c8fa8ef30 100644 --- a/client/src/app/+videos/video-list/videos-list-common-page.component.ts +++ b/client/src/app/+videos/video-list/videos-list-common-page.component.ts | |||
@@ -204,13 +204,28 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable | |||
204 | if ([ 'hot', 'trending', 'likes', 'views' ].includes(sanitizedSort)) { | 204 | if ([ 'hot', 'trending', 'likes', 'views' ].includes(sanitizedSort)) { |
205 | this.title = $localize`Trending` | 205 | this.title = $localize`Trending` |
206 | 206 | ||
207 | if (sanitizedSort === 'hot') this.titleTooltip = $localize`Videos with the most interactions for recent videos` | 207 | if (sanitizedSort === 'hot') { |
208 | if (sanitizedSort === 'likes') this.titleTooltip = $localize`Videos that have the most likes` | 208 | this.titleTooltip = $localize`Videos with the most interactions for recent videos` |
209 | if (sanitizedSort === 'views') this.titleTooltip = undefined | 209 | return |
210 | } | ||
211 | |||
212 | if (sanitizedSort === 'likes') { | ||
213 | this.titleTooltip = $localize`Videos that have the most likes` | ||
214 | return | ||
215 | } | ||
216 | |||
217 | if (sanitizedSort === 'views') { | ||
218 | this.titleTooltip = undefined | ||
219 | return | ||
220 | } | ||
210 | 221 | ||
211 | if (sanitizedSort === 'trending') { | 222 | if (sanitizedSort === 'trending') { |
212 | if (this.trendingDays === 1) this.titleTooltip = $localize`Videos with the most views during the last 24 hours` | 223 | if (this.trendingDays === 1) { |
213 | else this.titleTooltip = $localize`Videos with the most views during the last ${this.trendingDays} days` | 224 | this.titleTooltip = $localize`Videos with the most views during the last 24 hours` |
225 | return | ||
226 | } | ||
227 | |||
228 | this.titleTooltip = $localize`Videos with the most views during the last ${this.trendingDays} days` | ||
214 | } | 229 | } |
215 | 230 | ||
216 | return | 231 | return |
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 6969329e8..629c04e6b 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -53,9 +53,7 @@ | |||
53 | <p>{{ message.detail }}</p> | 53 | <p>{{ message.detail }}</p> |
54 | </div> | 54 | </div> |
55 | 55 | ||
56 | <span *ngIf="message.severity === 'success'" class="glyphicon glyphicon-ok"></span> | 56 | <my-global-icon [iconName]="getNotificationIcon(message)"></my-global-icon> |
57 | <span *ngIf="message.severity === 'info'" class="glyphicon glyphicon-info-sign"></span> | ||
58 | <span *ngIf="message.severity === 'error'" class="glyphicon glyphicon-remove"></span> | ||
59 | </div> | 57 | </div> |
60 | </ng-template> | 58 | </ng-template> |
61 | </p-toast> | 59 | </p-toast> |
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 5f3e15d80..31e9987c6 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss | |||
@@ -103,14 +103,4 @@ | |||
103 | cursor: pointer; | 103 | cursor: pointer; |
104 | width: 20px; | 104 | width: 20px; |
105 | } | 105 | } |
106 | |||
107 | ::ng-deep { | ||
108 | p { | ||
109 | font-size: 16px; | ||
110 | } | ||
111 | |||
112 | p:last-child { | ||
113 | margin-bottom: 0; | ||
114 | } | ||
115 | } | ||
116 | } | 106 | } |
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index a60138af9..8fdab0c40 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -31,6 +31,7 @@ import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/model | |||
31 | import { MenuService } from './core/menu/menu.service' | 31 | import { MenuService } from './core/menu/menu.service' |
32 | import { POP_STATE_MODAL_DISMISS } from './helpers' | 32 | import { POP_STATE_MODAL_DISMISS } from './helpers' |
33 | import { InstanceService } from './shared/shared-instance' | 33 | import { InstanceService } from './shared/shared-instance' |
34 | import { GlobalIconName } from './shared/shared-icons' | ||
34 | 35 | ||
35 | @Component({ | 36 | @Component({ |
36 | selector: 'my-app', | 37 | selector: 'my-app', |
@@ -150,6 +151,17 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
150 | this.screenService.isBroadcastMessageDisplayed = false | 151 | this.screenService.isBroadcastMessageDisplayed = false |
151 | } | 152 | } |
152 | 153 | ||
154 | getNotificationIcon (message: { severity: 'success' | 'error' | 'info' }): GlobalIconName { | ||
155 | switch (message.severity) { | ||
156 | case 'error': | ||
157 | return 'cross' | ||
158 | case 'success': | ||
159 | return 'tick' | ||
160 | case 'info': | ||
161 | return 'help' | ||
162 | } | ||
163 | } | ||
164 | |||
153 | private initRouteEvents () { | 165 | private initRouteEvents () { |
154 | const eventsObs = this.router.events | 166 | const eventsObs = this.router.events |
155 | 167 | ||
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index d80f95ed6..4a4c2321b 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts | |||
@@ -41,7 +41,9 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra | |||
41 | ToastModule, | 41 | ToastModule, |
42 | 42 | ||
43 | HotkeyModule.forRoot({ | 43 | HotkeyModule.forRoot({ |
44 | cheatSheetCloseEsc: true | 44 | cheatSheetCloseEsc: true, |
45 | cheatSheetDescription: $localize`Show/hide this help menu`, | ||
46 | cheatSheetCloseEscDescription: $localize`Hide this help menu` | ||
45 | }) | 47 | }) |
46 | ], | 48 | ], |
47 | 49 | ||
diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index f0dc4fcaa..81837db7e 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts | |||
@@ -132,7 +132,7 @@ export class MenuService { | |||
132 | path: '/videos/trending' | 132 | path: '/videos/trending' |
133 | }, | 133 | }, |
134 | { | 134 | { |
135 | icon: 'recently-added' as 'recently-added', | 135 | icon: 'add' as 'add', |
136 | label: $localize`Recently added videos`, | 136 | label: $localize`Recently added videos`, |
137 | shortLabel: $localize`Recently added`, | 137 | shortLabel: $localize`Recently added`, |
138 | path: '/videos/recently-added' | 138 | path: '/videos/recently-added' |
diff --git a/client/src/app/core/notification/peertube-socket.service.ts b/client/src/app/core/notification/peertube-socket.service.ts index 0db86d8e7..50a11e948 100644 --- a/client/src/app/core/notification/peertube-socket.service.ts +++ b/client/src/app/core/notification/peertube-socket.service.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Subject } from 'rxjs' | 1 | import { Subject } from 'rxjs' |
2 | import { io, Socket } from 'socket.io-client' | 2 | import { ManagerOptions, Socket, SocketOptions } from 'socket.io-client' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { LiveVideoEventPayload, LiveVideoEventType, UserNotification as UserNotificationServer } from '@shared/models' | 4 | import { LiveVideoEventPayload, LiveVideoEventType, UserNotification as UserNotificationServer } from '@shared/models' |
5 | import { environment } from '../../../environments/environment' | 5 | import { environment } from '../../../environments/environment' |
@@ -9,7 +9,7 @@ export type NotificationEvent = 'new' | 'read' | 'read-all' | |||
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class PeerTubeSocket { | 11 | export class PeerTubeSocket { |
12 | private io: typeof io | 12 | private io: (uri: string, opts?: Partial<ManagerOptions & SocketOptions>) => Socket |
13 | 13 | ||
14 | private notificationSubject = new Subject<{ type: NotificationEvent, notification?: UserNotificationServer }>() | 14 | private notificationSubject = new Subject<{ type: NotificationEvent, notification?: UserNotificationServer }>() |
15 | private liveVideosSubject = new Subject<{ type: LiveVideoEventType, payload: LiveVideoEventPayload }>() | 15 | private liveVideosSubject = new Subject<{ type: LiveVideoEventType, payload: LiveVideoEventPayload }>() |
diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts index 17053811c..86c7484a5 100644 --- a/client/src/app/core/rest/rest-extractor.service.ts +++ b/client/src/app/core/rest/rest-extractor.service.ts | |||
@@ -34,49 +34,17 @@ export class RestExtractor { | |||
34 | return target | 34 | return target |
35 | } | 35 | } |
36 | 36 | ||
37 | handleError (err: any) { | 37 | redirectTo404IfNotFound (obj: { status: number }, type: 'video' | 'other', status = [ HttpStatusCode.NOT_FOUND_404 ]) { |
38 | let errorMessage | 38 | if (obj?.status && status.includes(obj.status)) { |
39 | // Do not use redirectService to avoid circular dependencies | ||
40 | this.router.navigate([ '/404' ], { state: { type, obj }, skipLocationChange: true }) | ||
41 | } | ||
39 | 42 | ||
40 | if (err.error instanceof Error) { | 43 | return observableThrowError(() => obj) |
41 | // A client-side or network error occurred. Handle it accordingly. | 44 | } |
42 | errorMessage = err.error.detail || err.error.title | ||
43 | console.error('An error occurred:', errorMessage) | ||
44 | } else if (typeof err.error === 'string') { | ||
45 | errorMessage = err.error | ||
46 | } else if (err.status !== undefined) { | ||
47 | // A server-side error occurred. | ||
48 | if (err.error?.errors) { | ||
49 | const errors = err.error.errors | ||
50 | const errorsArray: string[] = [] | ||
51 | |||
52 | Object.keys(errors).forEach(key => { | ||
53 | errorsArray.push(errors[key].msg) | ||
54 | }) | ||
55 | |||
56 | errorMessage = errorsArray.join('. ') | ||
57 | } else if (err.error?.error) { | ||
58 | errorMessage = err.error.error | ||
59 | } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) { | ||
60 | // eslint-disable-next-line max-len | ||
61 | errorMessage = $localize`Media is too large for the server. Please contact you administrator if you want to increase the limit size.` | ||
62 | } else if (err.status === HttpStatusCode.TOO_MANY_REQUESTS_429) { | ||
63 | const secondsLeft = err.headers.get('retry-after') | ||
64 | if (secondsLeft) { | ||
65 | const minutesLeft = Math.floor(parseInt(secondsLeft, 10) / 60) | ||
66 | errorMessage = $localize`Too many attempts, please try again after ${minutesLeft} minutes.` | ||
67 | } else { | ||
68 | errorMessage = $localize`Too many attempts, please try again later.` | ||
69 | } | ||
70 | } else if (err.status === HttpStatusCode.INTERNAL_SERVER_ERROR_500) { | ||
71 | errorMessage = $localize`Server error. Please retry later.` | ||
72 | } | ||
73 | 45 | ||
74 | errorMessage = errorMessage || 'Unknown error.' | 46 | handleError (err: any) { |
75 | console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) | 47 | const errorMessage = this.buildErrorMessage(err) |
76 | } else { | ||
77 | console.error(err) | ||
78 | errorMessage = err | ||
79 | } | ||
80 | 48 | ||
81 | const errorObj: { message: string, status: string, body: string } = { | 49 | const errorObj: { message: string, status: string, body: string } = { |
82 | message: errorMessage, | 50 | message: errorMessage, |
@@ -92,12 +60,63 @@ export class RestExtractor { | |||
92 | return observableThrowError(() => errorObj) | 60 | return observableThrowError(() => errorObj) |
93 | } | 61 | } |
94 | 62 | ||
95 | redirectTo404IfNotFound (obj: { status: number }, type: 'video' | 'other', status = [ HttpStatusCode.NOT_FOUND_404 ]) { | 63 | private buildErrorMessage (err: any) { |
96 | if (obj?.status && status.includes(obj.status)) { | 64 | if (err.error instanceof Error) { |
97 | // Do not use redirectService to avoid circular dependencies | 65 | // A client-side or network error occurred. Handle it accordingly. |
98 | this.router.navigate([ '/404' ], { state: { type, obj }, skipLocationChange: true }) | 66 | const errorMessage = err.error.detail || err.error.title |
67 | console.error('An error occurred:', errorMessage) | ||
68 | |||
69 | return errorMessage | ||
99 | } | 70 | } |
100 | 71 | ||
101 | return observableThrowError(() => obj) | 72 | if (typeof err.error === 'string') { |
73 | return err.error | ||
74 | } | ||
75 | |||
76 | if (err.status !== undefined) { | ||
77 | const errorMessage = this.buildServerErrorMessage(err) | ||
78 | console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) | ||
79 | |||
80 | return errorMessage | ||
81 | } | ||
82 | |||
83 | console.error(err) | ||
84 | return err | ||
85 | } | ||
86 | |||
87 | private buildServerErrorMessage (err: any) { | ||
88 | // A server-side error occurred. | ||
89 | if (err.error?.errors) { | ||
90 | const errors = err.error.errors | ||
91 | |||
92 | return Object.keys(errors) | ||
93 | .map(key => errors[key].msg) | ||
94 | .join('. ') | ||
95 | } | ||
96 | |||
97 | if (err.error?.error) { | ||
98 | return err.error.error | ||
99 | } | ||
100 | |||
101 | if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) { | ||
102 | return $localize`Media is too large for the server. Please contact you administrator if you want to increase the limit size.` | ||
103 | } | ||
104 | |||
105 | if (err.status === HttpStatusCode.TOO_MANY_REQUESTS_429) { | ||
106 | const secondsLeft = err.headers.get('retry-after') | ||
107 | |||
108 | if (secondsLeft) { | ||
109 | const minutesLeft = Math.floor(parseInt(secondsLeft, 10) / 60) | ||
110 | return $localize`Too many attempts, please try again after ${minutesLeft} minutes.` | ||
111 | } | ||
112 | |||
113 | return $localize`Too many attempts, please try again later.` | ||
114 | } | ||
115 | |||
116 | if (err.status === HttpStatusCode.INTERNAL_SERVER_ERROR_500) { | ||
117 | return $localize`Server error. Please retry later.` | ||
118 | } | ||
119 | |||
120 | return $localize`Unknown server error` | ||
102 | } | 121 | } |
103 | } | 122 | } |
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts index d8b039187..7b765f7fc 100644 --- a/client/src/app/core/rest/rest-table.ts +++ b/client/src/app/core/rest/rest-table.ts | |||
@@ -39,6 +39,10 @@ export abstract class RestTable { | |||
39 | } | 39 | } |
40 | } | 40 | } |
41 | 41 | ||
42 | saveSort () { | ||
43 | peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) | ||
44 | } | ||
45 | |||
42 | loadLazy (event: LazyLoadEvent) { | 46 | loadLazy (event: LazyLoadEvent) { |
43 | logger('Load lazy %o.', event) | 47 | logger('Load lazy %o.', event) |
44 | 48 | ||
@@ -60,10 +64,6 @@ export abstract class RestTable { | |||
60 | this.saveSort() | 64 | this.saveSort() |
61 | } | 65 | } |
62 | 66 | ||
63 | saveSort () { | ||
64 | peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) | ||
65 | } | ||
66 | |||
67 | onSearch (search: string) { | 67 | onSearch (search: string) { |
68 | this.search = search | 68 | this.search = search |
69 | this.reloadData() | 69 | this.reloadData() |
diff --git a/client/src/app/core/routing/scroll.service.ts b/client/src/app/core/routing/scroll.service.ts index bd5076502..6d37fde71 100644 --- a/client/src/app/core/routing/scroll.service.ts +++ b/client/src/app/core/routing/scroll.service.ts | |||
@@ -67,7 +67,7 @@ export class ScrollService { | |||
67 | private consumeScroll () { | 67 | private consumeScroll () { |
68 | // Handle anchors/restore position | 68 | // Handle anchors/restore position |
69 | this.peertubeRouter.getScrollEvents().subscribe(e => { | 69 | this.peertubeRouter.getScrollEvents().subscribe(e => { |
70 | logger('Will schedule scroll after router event %o.', e) | 70 | logger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll }) |
71 | 71 | ||
72 | // scrollToAnchor first to preserve anchor position when using history navigation | 72 | // scrollToAnchor first to preserve anchor position when using history navigation |
73 | if (e.anchor) { | 73 | if (e.anchor) { |
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts index e88511054..40939ecb8 100644 --- a/client/src/app/core/theme/theme.service.ts +++ b/client/src/app/core/theme/theme.service.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { capitalizeFirstLetter } from '@root-helpers/string' | ||
2 | import { UserLocalStorageKeys } from '@root-helpers/users' | 3 | import { UserLocalStorageKeys } from '@root-helpers/users' |
3 | import { HTMLServerConfig, ServerConfigTheme } from '@shared/models' | 4 | import { HTMLServerConfig, ServerConfigTheme } from '@shared/models' |
4 | import { environment } from '../../../environments/environment' | 5 | import { environment } from '../../../environments/environment' |
@@ -40,6 +41,19 @@ export class ThemeService { | |||
40 | this.listenUserTheme() | 41 | this.listenUserTheme() |
41 | } | 42 | } |
42 | 43 | ||
44 | getDefaultThemeLabel () { | ||
45 | if (this.hasDarkTheme()) { | ||
46 | return $localize`Light/Orange or Dark` | ||
47 | } | ||
48 | |||
49 | return $localize`Light/Orange` | ||
50 | } | ||
51 | |||
52 | buildAvailableThemes () { | ||
53 | return this.serverConfig.theme.registered | ||
54 | .map(t => ({ id: t.name, label: capitalizeFirstLetter(t.name) })) | ||
55 | } | ||
56 | |||
43 | private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) { | 57 | private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) { |
44 | this.themes = themes | 58 | this.themes = themes |
45 | 59 | ||
@@ -81,10 +95,7 @@ export class ThemeService { | |||
81 | if (instanceTheme !== 'default') return instanceTheme | 95 | if (instanceTheme !== 'default') return instanceTheme |
82 | 96 | ||
83 | // Default to dark theme if available and wanted by the user | 97 | // Default to dark theme if available and wanted by the user |
84 | if ( | 98 | if (this.hasDarkTheme() && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { |
85 | this.themes.find(t => t.name === 'dark') && | ||
86 | window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches | ||
87 | ) { | ||
88 | return 'dark' | 99 | return 'dark' |
89 | } | 100 | } |
90 | 101 | ||
@@ -193,4 +204,8 @@ export class ThemeService { | |||
193 | private getTheme (name: string) { | 204 | private getTheme (name: string) { |
194 | return this.themes.find(t => t.name === name) | 205 | return this.themes.find(t => t.name === name) |
195 | } | 206 | } |
207 | |||
208 | private hasDarkTheme () { | ||
209 | return this.serverConfig.theme.registered.some(t => t.name === 'dark') | ||
210 | } | ||
196 | } | 211 | } |
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss index de483086b..8a4111c5a 100644 --- a/client/src/app/header/header.component.scss +++ b/client/src/app/header/header.component.scss | |||
@@ -2,7 +2,11 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | my-search-typeahead { | 4 | my-search-typeahead { |
5 | @include margin-right(15px); | 5 | @include margin-right(80px); |
6 | |||
7 | @media screen and (max-width: $small-view) { | ||
8 | @include margin-right(15px); | ||
9 | } | ||
6 | } | 10 | } |
7 | 11 | ||
8 | .publish-button { | 12 | .publish-button { |
@@ -11,7 +15,7 @@ my-search-typeahead { | |||
11 | @include button-with-icon(22px, 3px, -1px); | 15 | @include button-with-icon(22px, 3px, -1px); |
12 | @include margin-right(25px); | 16 | @include margin-right(25px); |
13 | 17 | ||
14 | @media screen and (max-width: 600px) { | 18 | @media screen and (max-width: $mobile-view) { |
15 | @include margin-right(10px); | 19 | @include margin-right(10px); |
16 | 20 | ||
17 | padding: 0 10px; | 21 | padding: 0 10px; |
diff --git a/client/src/app/header/search-typeahead.component.html b/client/src/app/header/search-typeahead.component.html index 7a9b6c51f..783b4b53b 100644 --- a/client/src/app/header/search-typeahead.component.html +++ b/client/src/app/header/search-typeahead.component.html | |||
@@ -25,8 +25,7 @@ | |||
25 | <div class="d-flex justify-content-between"> | 25 | <div class="d-flex justify-content-between"> |
26 | <label class="small-title" i18n>GLOBAL SEARCH</label> | 26 | <label class="small-title" i18n>GLOBAL SEARCH</label> |
27 | <div class="advanced-search-status muted"> | 27 | <div class="advanced-search-status muted"> |
28 | <span *ngIf="serverConfig" class="mr-1" i18n>using {{ serverConfig.search.searchIndex.url }}</span> | 28 | <span *ngIf="serverConfig" class="me-1" i18n>using {{ serverConfig.search.searchIndex.url }}</span> |
29 | <i class="glyphicon glyphicon-globe"></i> | ||
30 | </div> | 29 | </div> |
31 | </div> | 30 | </div> |
32 | <div class="muted" i18n>Results will be augmented with those of a third-party index. Only data necessary to make the query will be sent.</div> | 31 | <div class="muted" i18n>Results will be augmented with those of a third-party index. Only data necessary to make the query will be sent.</div> |
@@ -39,9 +38,8 @@ | |||
39 | <label class="small-title" i18n>ADVANCED SEARCH</label> | 38 | <label class="small-title" i18n>ADVANCED SEARCH</label> |
40 | <div class="advanced-search-status c-help"> | 39 | <div class="advanced-search-status c-help"> |
41 | <span [ngClass]="canSearchAnyURI ? 'text-success' : 'muted'" i18n-title title="Determines whether you can resolve any distant content, or if this instance only allows doing so for instances it follows."> | 40 | <span [ngClass]="canSearchAnyURI ? 'text-success' : 'muted'" i18n-title title="Determines whether you can resolve any distant content, or if this instance only allows doing so for instances it follows."> |
42 | <span *ngIf="canSearchAnyURI()" class="mr-1" i18n>any instance</span> | 41 | <span *ngIf="canSearchAnyURI()" class="me-1" i18n>any instance</span> |
43 | <span *ngIf="!canSearchAnyURI()" class="mr-1" i18n>only followed instances</span> | 42 | <span *ngIf="!canSearchAnyURI()" class="me-1" i18n>only followed instances</span> |
44 | <i [ngClass]="canSearchAnyURI() ? 'glyphicon glyphicon-ok-sign' : 'glyphicon glyphicon-exclamation-sign'"></i> | ||
45 | </span> | 43 | </span> |
46 | </div> | 44 | </div> |
47 | </div> | 45 | </div> |
diff --git a/client/src/app/header/search-typeahead.component.scss b/client/src/app/header/search-typeahead.component.scss index 5114ec3a7..e4c9c602c 100644 --- a/client/src/app/header/search-typeahead.component.scss +++ b/client/src/app/header/search-typeahead.component.scss | |||
@@ -129,10 +129,6 @@ li.suggestion { | |||
129 | } | 129 | } |
130 | } | 130 | } |
131 | 131 | ||
132 | .glyphicon { | ||
133 | top: 3px; | ||
134 | } | ||
135 | |||
136 | .advanced-search-status { | 132 | .advanced-search-status { |
137 | height: max-content; | 133 | height: max-content; |
138 | cursor: default; | 134 | cursor: default; |
diff --git a/client/src/app/header/suggestion.component.html b/client/src/app/header/suggestion.component.html index 4ac9809e1..3f85ed6ae 100644 --- a/client/src/app/header/suggestion.component.html +++ b/client/src/app/header/suggestion.component.html | |||
@@ -1,16 +1,16 @@ | |||
1 | <a tabindex="-1" class="d-flex flex-auto flex-items-center p-2" [class.focus-visible]="active"> | 1 | <a tabindex="-1" class="d-flex flex-auto flex-items-center p-2" [class.focus-visible]="active"> |
2 | <div class="flex-shrink-0 mr-2 text-center"> | 2 | <div class="flex-shrink-0 me-2 text-center"> |
3 | <my-global-icon iconName="search"></my-global-icon> | 3 | <my-global-icon iconName="search"></my-global-icon> |
4 | </div> | 4 | </div> |
5 | 5 | ||
6 | <img class="avatar mr-2 flex-shrink-0 d-none" alt="" aria-label="Team" src="" width="28" height="28"> | 6 | <img class="avatar me-2 flex-shrink-0 d-none" alt="" aria-label="Team" src="" width="28" height="28"> |
7 | 7 | ||
8 | <div | 8 | <div |
9 | class="flex-auto overflow-hidden text-left no-wrap css-truncate css-truncate-target" | 9 | class="flex-auto overflow-hidden text-start no-wrap css-truncate css-truncate-target" |
10 | [attr.aria-label]="result.text" [innerHTML]="result.text | highlight : highlight" | 10 | [attr.aria-label]="result.text" [innerHTML]="result.text | highlight : highlight" |
11 | ></div> | 11 | ></div> |
12 | 12 | ||
13 | <div class="border rounded flex-shrink-0 px-1 bg-gray text-gray-light ml-1 f6"> | 13 | <div class="border rounded flex-shrink-0 px-1 bg-gray text-gray-light ms-1 f6"> |
14 | <span *ngIf="result.type === 'search-instance'" i18n>In this instance's network</span> | 14 | <span *ngIf="result.type === 'search-instance'" i18n>In this instance's network</span> |
15 | <span *ngIf="result.type === 'search-index'" i18n>In the vidiverse</span> | 15 | <span *ngIf="result.type === 'search-index'" i18n>In the vidiverse</span> |
16 | </div> | 16 | </div> |
diff --git a/client/src/app/helpers/i18n-utils.ts b/client/src/app/helpers/i18n-utils.ts index bbfb12959..2017a31ea 100644 --- a/client/src/app/helpers/i18n-utils.ts +++ b/client/src/app/helpers/i18n-utils.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { environment } from '../../environments/environment' | 1 | import { environment } from '../../environments/environment' |
2 | import IntlMessageFormat from 'intl-messageformat' | ||
2 | 3 | ||
3 | function isOnDevLocale () { | 4 | function isOnDevLocale () { |
4 | return environment.production === false && window.location.search === '?lang=fr' | 5 | return environment.production === false && window.location.search === '?lang=fr' |
@@ -8,7 +9,31 @@ function getDevLocale () { | |||
8 | return 'fr-FR' | 9 | return 'fr-FR' |
9 | } | 10 | } |
10 | 11 | ||
12 | function prepareIcu (icu: string) { | ||
13 | let alreadyWarned = false | ||
14 | |||
15 | try { | ||
16 | const msg = new IntlMessageFormat(icu, $localize.locale) | ||
17 | |||
18 | return (context: { [id: string]: number | string }, fallback: string) => { | ||
19 | try { | ||
20 | return msg.format(context) as string | ||
21 | } catch (err) { | ||
22 | if (!alreadyWarned) console.warn('Cannot format ICU %s.', icu, err) | ||
23 | |||
24 | alreadyWarned = true | ||
25 | return fallback | ||
26 | } | ||
27 | } | ||
28 | } catch (err) { | ||
29 | console.warn('Cannot build intl message %s.', icu, err) | ||
30 | |||
31 | return (_context: unknown, fallback: string) => fallback | ||
32 | } | ||
33 | } | ||
34 | |||
11 | export { | 35 | export { |
12 | getDevLocale, | 36 | getDevLocale, |
37 | prepareIcu, | ||
13 | isOnDevLocale | 38 | isOnDevLocale |
14 | } | 39 | } |
diff --git a/client/src/app/helpers/utils/upload.ts b/client/src/app/helpers/utils/upload.ts index a3fce7fee..5c2600a0d 100644 --- a/client/src/app/helpers/utils/upload.ts +++ b/client/src/app/helpers/utils/upload.ts | |||
@@ -2,36 +2,43 @@ import { HttpErrorResponse } from '@angular/common/http' | |||
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { HttpStatusCode } from '@shared/models' | 3 | import { HttpStatusCode } from '@shared/models' |
4 | 4 | ||
5 | function genericUploadErrorHandler (parameters: { | 5 | function genericUploadErrorHandler (options: { |
6 | err: Pick<HttpErrorResponse, 'message' | 'status' | 'headers'> | 6 | err: Pick<HttpErrorResponse, 'message' | 'status' | 'headers'> |
7 | name: string | 7 | name: string |
8 | notifier: Notifier | 8 | notifier: Notifier |
9 | sticky?: boolean | 9 | sticky?: boolean |
10 | }) { | 10 | }) { |
11 | const { err, name, notifier, sticky } = { sticky: false, ...parameters } | 11 | const { err, name, notifier, sticky = false } = options |
12 | const title = $localize`The upload failed` | 12 | const title = $localize`Upload failed` |
13 | let message = err.message | 13 | const message = buildMessage(name, err) |
14 | |||
15 | if (err instanceof ErrorEvent) { // network error | ||
16 | message = $localize`The connection was interrupted` | ||
17 | notifier.error(message, title, null, sticky) | ||
18 | } else if (err.status === HttpStatusCode.INTERNAL_SERVER_ERROR_500) { | ||
19 | message = $localize`The server encountered an error` | ||
20 | notifier.error(message, title, null, sticky) | ||
21 | } else if (err.status === HttpStatusCode.REQUEST_TIMEOUT_408) { | ||
22 | message = $localize`Your ${name} file couldn't be transferred before the set timeout (usually 10min)` | ||
23 | notifier.error(message, title, null, sticky) | ||
24 | } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) { | ||
25 | const maxFileSize = err.headers?.get('X-File-Maximum-Size') || '8G' | ||
26 | message = $localize`Your ${name} file was too large (max. size: ${maxFileSize})` | ||
27 | notifier.error(message, title, null, sticky) | ||
28 | } else { | ||
29 | notifier.error(err.message, title) | ||
30 | } | ||
31 | 14 | ||
15 | notifier.error(message, title, null, sticky) | ||
32 | return message | 16 | return message |
33 | } | 17 | } |
34 | 18 | ||
35 | export { | 19 | export { |
36 | genericUploadErrorHandler | 20 | genericUploadErrorHandler |
37 | } | 21 | } |
22 | |||
23 | // --------------------------------------------------------------------------- | ||
24 | |||
25 | function buildMessage (name: string, err: Pick<HttpErrorResponse, 'message' | 'status' | 'headers'>) { | ||
26 | if (err instanceof ErrorEvent) { // network error | ||
27 | return $localize`The connection was interrupted` | ||
28 | } | ||
29 | |||
30 | if (err.status === HttpStatusCode.INTERNAL_SERVER_ERROR_500) { | ||
31 | return $localize`The server encountered an error` | ||
32 | } | ||
33 | |||
34 | if (err.status === HttpStatusCode.REQUEST_TIMEOUT_408) { | ||
35 | return $localize`Your ${name} file couldn't be transferred before the server proxy timeout` | ||
36 | } | ||
37 | |||
38 | if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) { | ||
39 | const maxFileSize = err.headers?.get('X-File-Maximum-Size') || '8G' | ||
40 | return $localize`Your ${name} file was too large (max. size: ${maxFileSize})` | ||
41 | } | ||
42 | |||
43 | return err.message | ||
44 | } | ||
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss index 04b18af33..28d58a678 100644 --- a/client/src/app/menu/language-chooser.component.scss +++ b/client/src/app/menu/language-chooser.component.scss | |||
@@ -18,7 +18,6 @@ | |||
18 | 18 | ||
19 | a { | 19 | a { |
20 | display: block; | 20 | display: block; |
21 | font-size: 16px; | ||
22 | margin: 15px; | 21 | margin: 15px; |
23 | } | 22 | } |
24 | } | 23 | } |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index b8f8d68ab..1a9ac9e8b 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -8,7 +8,8 @@ | |||
8 | [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside" | 8 | [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside" |
9 | > | 9 | > |
10 | <div ngbDropdownToggle> | 10 | <div ngbDropdownToggle> |
11 | <my-actor-avatar [account]="user.account" size="34"></my-actor-avatar> | 11 | <my-actor-avatar [actor]="user.account" actorType="account" size="34"></my-actor-avatar> |
12 | |||
12 | <div class="logged-in-info"> | 13 | <div class="logged-in-info"> |
13 | <div class="logged-in-display-name">{{ user.account?.displayName }}</div> | 14 | <div class="logged-in-display-name">{{ user.account?.displayName }}</div> |
14 | 15 | ||
@@ -16,7 +17,7 @@ | |||
16 | </div> | 17 | </div> |
17 | 18 | ||
18 | <div class="dropdown-toggle-indicator"> | 19 | <div class="dropdown-toggle-indicator"> |
19 | <span class="glyphicon glyphicon-chevron-down"></span> | 20 | <span class="chevron-down"></span> |
20 | </div> | 21 | </div> |
21 | </div> | 22 | </div> |
22 | 23 | ||
@@ -36,31 +37,31 @@ | |||
36 | > | 37 | > |
37 | <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> | 38 | <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> |
38 | <span i18n>Interface:</span> | 39 | <span i18n>Interface:</span> |
39 | <span class="ml-auto muted">{{ currentInterfaceLanguage }}</span> | 40 | <span class="ms-auto muted">{{ currentInterfaceLanguage }}</span> |
40 | </a> | 41 | </a> |
41 | 42 | ||
42 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-languages-subtitles" | 43 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-languages-subtitles" |
43 | #settingsLanguagesSubtitles (click)="onActiveLinkScrollToAnchor(settingsLanguagesSubtitles)"> | 44 | #settingsLanguagesSubtitles (click)="onActiveLinkScrollToAnchor(settingsLanguagesSubtitles)"> |
44 | <my-global-icon iconName="video-lang" aria-hidden="true"></my-global-icon> | 45 | <my-global-icon iconName="video-lang" aria-hidden="true"></my-global-icon> |
45 | <span i18n>Videos:</span> | 46 | <span i18n>Videos:</span> |
46 | <span class="ml-auto muted">{{ videoLanguages.join(', ') }}</span> | 47 | <span class="ms-auto muted">{{ videoLanguages.join(', ') }}</span> |
47 | </a> | 48 | </a> |
48 | 49 | ||
49 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item settings-sensitive" routerLink="/my-account/settings" | 50 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item settings-sensitive" routerLink="/my-account/settings" |
50 | fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy | 51 | fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy |
51 | (click)="onActiveLinkScrollToAnchor(settingsSensitiveContentPolicy)" | 52 | (click)="onActiveLinkScrollToAnchor(settingsSensitiveContentPolicy)" |
52 | > | 53 | > |
53 | <my-global-icon class="hover-display-toggle" [hidden]="user.nsfwPolicy === 'display'" iconName="sensitive" aria-hidden="true"></my-global-icon> | 54 | <my-global-icon class="hover-display-toggle" [hidden]="user.nsfwPolicy === 'display'" iconName="eye-open" aria-hidden="true"></my-global-icon> |
54 | <my-global-icon class="hover-display-toggle" [hidden]="user.nsfwPolicy !== 'display'" iconName="unsensitive" aria-hidden="true"></my-global-icon> | 55 | <my-global-icon class="hover-display-toggle" [hidden]="user.nsfwPolicy !== 'display'" iconName="eye-close" aria-hidden="true"></my-global-icon> |
55 | <span i18n>Sensitive:</span> | 56 | <span i18n>Sensitive:</span> |
56 | <span class="ml-auto muted">{{ nsfwPolicy }}</span> | 57 | <span class="ms-auto muted">{{ nsfwPolicy }}</span> |
57 | </a> | 58 | </a> |
58 | 59 | ||
59 | <a ngbDropdownItem class="dropdown-item" (click)="toggleUseP2P()"> | 60 | <a ngbDropdownItem class="dropdown-item" (click)="toggleUseP2P()"> |
60 | <my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon> | 61 | <my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon> |
61 | <ng-container i18n>Help share videos</ng-container> | 62 | <ng-container i18n>Help share videos</ng-container> |
62 | 63 | ||
63 | <my-input-switch class="ml-auto" [checked]="user.p2pEnabled"></my-input-switch> | 64 | <my-input-switch class="ms-auto" [checked]="user.p2pEnabled"></my-input-switch> |
64 | </a> | 65 | </a> |
65 | 66 | ||
66 | <div class="dropdown-divider"></div> | 67 | <div class="dropdown-divider"></div> |
@@ -149,7 +150,7 @@ | |||
149 | 150 | ||
150 | <div class="footer-copyleft"> | 151 | <div class="footer-copyleft"> |
151 | <small class="d-inline" i18n-title title="powered by PeerTube - CopyLeft 2015-2022"> | 152 | <small class="d-inline" i18n-title title="powered by PeerTube - CopyLeft 2015-2022"> |
152 | <a href="https://joinpeertube.org" class="mr-1" target="_blank" rel="noopener noreferrer" i18n>powered by PeerTube</a> | 153 | <a href="https://joinpeertube.org" class="me-1" target="_blank" rel="noopener noreferrer" i18n>powered by PeerTube</a> |
153 | 154 | ||
154 | <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" target="_blank" rel="noopener noreferrer"> | 155 | <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" target="_blank" rel="noopener noreferrer"> |
155 | <span aria-label="copyleft" class="d-inline-block" style="transform: rotateY(180deg)">©</span> 2015-2022 | 156 | <span aria-label="copyleft" class="d-inline-block" style="transform: rotateY(180deg)">©</span> 2015-2022 |
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 808a33a4a..a548c08cf 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -16,7 +16,6 @@ $footer-links-base-opacity: .8; | |||
16 | 16 | ||
17 | color: pvar(--menuForegroundColor); | 17 | color: pvar(--menuForegroundColor); |
18 | cursor: pointer; | 18 | cursor: pointer; |
19 | font-size: 16px; | ||
20 | white-space: normal; | 19 | white-space: normal; |
21 | word-break: break-word; | 20 | word-break: break-word; |
22 | transition: background-color .1s ease-in-out; | 21 | transition: background-color .1s ease-in-out; |
@@ -106,6 +105,7 @@ my-notification { | |||
106 | } | 105 | } |
107 | 106 | ||
108 | .logged-in-more { | 107 | .logged-in-more { |
108 | |||
109 | @mixin display-hints($is-mobile: false) { | 109 | @mixin display-hints($is-mobile: false) { |
110 | background-color: rgba(255, 255, 255, 0.15); | 110 | background-color: rgba(255, 255, 255, 0.15); |
111 | 111 | ||
@@ -114,18 +114,16 @@ my-notification { | |||
114 | display: inherit !important; | 114 | display: inherit !important; |
115 | } | 115 | } |
116 | 116 | ||
117 | .dropdown-toggle { | 117 | > .dropdown-toggle { |
118 | max-width: 88% !important; | 118 | max-width: 88% !important; |
119 | } | 119 | } |
120 | } | 120 | } |
121 | } | 121 | } |
122 | 122 | ||
123 | $main-radius: 25px; | ||
124 | |||
125 | @include margin-left(13px); | 123 | @include margin-left(13px); |
126 | 124 | ||
127 | flex: 1; | 125 | flex: 1; |
128 | border-radius: $main-radius; | 126 | border-radius: 25px; |
129 | transition: all .1s ease-in-out; | 127 | transition: all .1s ease-in-out; |
130 | cursor: pointer; | 128 | cursor: pointer; |
131 | line-height: 1; | 129 | line-height: 1; |
@@ -157,7 +155,6 @@ my-notification { | |||
157 | 155 | ||
158 | .dropdown-toggle-indicator { | 156 | .dropdown-toggle-indicator { |
159 | position: relative; | 157 | position: relative; |
160 | width: 0; | ||
161 | display: none; | 158 | display: none; |
162 | 159 | ||
163 | span { | 160 | span { |
@@ -165,21 +162,17 @@ my-notification { | |||
165 | right: -35px; | 162 | right: -35px; |
166 | top: -8px; | 163 | top: -8px; |
167 | color: #808080; | 164 | color: #808080; |
168 | width: $main-radius; | ||
169 | } | 165 | } |
170 | } | 166 | } |
171 | 167 | ||
172 | .dropdown-toggle { | 168 | .dropdown-toggle::after { |
173 | &::after { | 169 | border: 0; |
174 | border: 0; | ||
175 | } | ||
176 | } | 170 | } |
177 | 171 | ||
178 | .dropdown-toggle:first-child { | 172 | > .dropdown-toggle:first-child { |
179 | display: flex; | 173 | display: flex; |
180 | align-items: center; | 174 | align-items: center; |
181 | padding: 5px 7px; | 175 | padding: 5px 7px; |
182 | border-radius: $main-radius; | ||
183 | } | 176 | } |
184 | } | 177 | } |
185 | 178 | ||
@@ -201,7 +194,6 @@ my-actor-avatar { | |||
201 | .logged-in-display-name { | 194 | .logged-in-display-name { |
202 | @include disable-default-a-behaviour; | 195 | @include disable-default-a-behaviour; |
203 | 196 | ||
204 | font-size: 16px; | ||
205 | font-weight: $font-semibold; | 197 | font-weight: $font-semibold; |
206 | color: pvar(--menuForegroundColor); | 198 | color: pvar(--menuForegroundColor); |
207 | } | 199 | } |
@@ -325,7 +317,7 @@ my-actor-avatar { | |||
325 | color: pvar(--menuForegroundColor); | 317 | color: pvar(--menuForegroundColor); |
326 | opacity: $footer-links-base-opacity; | 318 | opacity: $footer-links-base-opacity; |
327 | white-space: nowrap; | 319 | white-space: nowrap; |
328 | font-size: 90%; | 320 | font-size: 0.75rem; |
329 | font-weight: 500; | 321 | font-weight: 500; |
330 | line-height: 1.4rem; | 322 | line-height: 1.4rem; |
331 | } | 323 | } |
@@ -358,10 +350,6 @@ my-actor-avatar { | |||
358 | display: flex; | 350 | display: flex; |
359 | align-items: center; | 351 | align-items: center; |
360 | 352 | ||
361 | i.glyphicon-menu-right { | ||
362 | opacity: .4; | ||
363 | } | ||
364 | |||
365 | &:hover { | 353 | &:hover { |
366 | .hover-display-toggle { | 354 | .hover-display-toggle { |
367 | display: none; | 355 | display: none; |
@@ -396,13 +384,6 @@ my-actor-avatar { | |||
396 | .dropdown-menu { | 384 | .dropdown-menu { |
397 | width: calc(100vw - 30px); | 385 | width: calc(100vw - 30px); |
398 | } | 386 | } |
399 | |||
400 | .dropdown-item:hover, | ||
401 | .dropdown-item:active { | ||
402 | &.settings-sensitive my-global-icon ::ng-deep svg { | ||
403 | margin-top: 0 !important; | ||
404 | } | ||
405 | } | ||
406 | } | 387 | } |
407 | 388 | ||
408 | my-global-icon { | 389 | my-global-icon { |
diff --git a/client/src/app/menu/notification.component.html b/client/src/app/menu/notification.component.html index beda1c43c..890b086f1 100644 --- a/client/src/app/menu/notification.component.html +++ b/client/src/app/menu/notification.component.html | |||
@@ -24,19 +24,24 @@ | |||
24 | <div> | 24 | <div> |
25 | <button | 25 | <button |
26 | *ngIf="unreadNotifications" | 26 | *ngIf="unreadNotifications" |
27 | i18n-title title="Mark all as read" class="glyphicon glyphicon-ok mr-2" | 27 | i18n-title title="Mark all as read" class="me-2" |
28 | (click)="markAllAsRead()" | 28 | (click)="markAllAsRead()" |
29 | ></button> | 29 | > |
30 | <my-global-icon iconName="tick"></my-global-icon> | ||
31 | </button> | ||
32 | |||
30 | <a | 33 | <a |
31 | i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog" | 34 | i18n-title title="Update your notification preferences" |
32 | routerLink="/my-account/settings" fragment="notifications" | 35 | routerLink="/my-account/settings" fragment="notifications" |
33 | #settingsNotifications (click)="onNavigate(settingsNotifications)" | 36 | #settingsNotifications (click)="onNavigate(settingsNotifications)" |
34 | ></a> | 37 | > |
38 | <my-global-icon iconName="cog"></my-global-icon> | ||
39 | </a> | ||
35 | </div> | 40 | </div> |
36 | </div> | 41 | </div> |
37 | 42 | ||
38 | <div *ngIf="!loaded" class="loader mt-4"> | 43 | <div *ngIf="!loaded" class="loader mt-4"> |
39 | <my-loader [loading]="!loaded"></my-loader> | 44 | <my-loader size="xl" [loading]="!loaded"></my-loader> |
40 | </div> | 45 | </div> |
41 | 46 | ||
42 | <my-user-notifications | 47 | <my-user-notifications |
@@ -45,7 +50,7 @@ | |||
45 | ></my-user-notifications> | 50 | ></my-user-notifications> |
46 | 51 | ||
47 | <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)"> | 52 | <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)"> |
48 | <my-global-icon class="mr-1" iconName="bell" aria-hidden="true"></my-global-icon> | 53 | <my-global-icon class="me-1" iconName="bell" aria-hidden="true"></my-global-icon> |
49 | <span i18n>See all your notifications</span> | 54 | <span i18n>See all your notifications</span> |
50 | </a> | 55 | </a> |
51 | </div> | 56 | </div> |
diff --git a/client/src/app/menu/notification.component.scss b/client/src/app/menu/notification.component.scss index 3515c3e19..7f72c98d1 100644 --- a/client/src/app/menu/notification.component.scss +++ b/client/src/app/menu/notification.component.scss | |||
@@ -119,6 +119,10 @@ | |||
119 | color: rgba(20, 20, 20, 0.8); | 119 | color: rgba(20, 20, 20, 0.8); |
120 | } | 120 | } |
121 | } | 121 | } |
122 | |||
123 | my-global-icon { | ||
124 | width: 20px; | ||
125 | } | ||
122 | } | 126 | } |
123 | 127 | ||
124 | .all-notifications { | 128 | .all-notifications { |
diff --git a/client/src/app/modal/account-setup-warning-modal.component.scss b/client/src/app/modal/account-setup-warning-modal.component.scss index d99edaf7a..25e67b08f 100644 --- a/client/src/app/modal/account-setup-warning-modal.component.scss +++ b/client/src/app/modal/account-setup-warning-modal.component.scss | |||
@@ -2,7 +2,6 @@ | |||
2 | @use '_variables' as *; | 2 | @use '_variables' as *; |
3 | 3 | ||
4 | .modal-body { | 4 | .modal-body { |
5 | font-size: 15px; | ||
6 | display: flex; | 5 | display: flex; |
7 | flex-direction: column; | 6 | flex-direction: column; |
8 | align-items: center; | 7 | align-items: center; |
@@ -24,7 +23,6 @@ | |||
24 | .subtitle { | 23 | .subtitle { |
25 | font-weight: $font-semibold; | 24 | font-weight: $font-semibold; |
26 | margin-bottom: 10px; | 25 | margin-bottom: 10px; |
27 | font-size: 16px; | ||
28 | } | 26 | } |
29 | 27 | ||
30 | li { | 28 | li { |
diff --git a/client/src/app/modal/admin-welcome-modal.component.html b/client/src/app/modal/admin-welcome-modal.component.html index f5d2b8799..b74d73185 100644 --- a/client/src/app/modal/admin-welcome-modal.component.html +++ b/client/src/app/modal/admin-welcome-modal.component.html | |||
@@ -56,7 +56,7 @@ | |||
56 | 56 | ||
57 | <p i18n> | 57 | <p i18n> |
58 | Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>, specifying <strong>who you are</strong>, | 58 | Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>, specifying <strong>who you are</strong>, |
59 | why <strong>you created your instance</strong> and <strong>how long</strong> you plan to <strong>maintain your it</strong> | 59 | why <strong>you created your instance</strong> and <strong>how long</strong> you plan to <strong>maintain it</strong> |
60 | is very important for visitors to understand on what type of instance they are. | 60 | is very important for visitors to understand on what type of instance they are. |
61 | </p> | 61 | </p> |
62 | 62 | ||
diff --git a/client/src/app/modal/admin-welcome-modal.component.scss b/client/src/app/modal/admin-welcome-modal.component.scss index 242a498d0..716f1fff0 100644 --- a/client/src/app/modal/admin-welcome-modal.component.scss +++ b/client/src/app/modal/admin-welcome-modal.component.scss | |||
@@ -1,10 +1,6 @@ | |||
1 | @use '_mixins' as *; | 1 | @use '_mixins' as *; |
2 | @use '_variables' as *; | 2 | @use '_variables' as *; |
3 | 3 | ||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .two-columns { | 4 | .two-columns { |
9 | display: flex; | 5 | display: flex; |
10 | align-items: center; | 6 | align-items: center; |
@@ -26,7 +22,6 @@ | |||
26 | .subtitle { | 22 | .subtitle { |
27 | font-weight: $font-semibold; | 23 | font-weight: $font-semibold; |
28 | margin-bottom: 10px; | 24 | margin-bottom: 10px; |
29 | font-size: 16px; | ||
30 | } | 25 | } |
31 | 26 | ||
32 | .block-documentation { | 27 | .block-documentation { |
@@ -63,7 +58,6 @@ li { | |||
63 | } | 58 | } |
64 | 59 | ||
65 | .link-title { | 60 | .link-title { |
66 | font-size: 16px; | ||
67 | font-weight: $font-semibold; | 61 | font-weight: $font-semibold; |
68 | display: flex; | 62 | display: flex; |
69 | justify-content: center; | 63 | justify-content: center; |
diff --git a/client/src/app/modal/confirm.component.html b/client/src/app/modal/confirm.component.html index f07501726..c59c25770 100644 --- a/client/src/app/modal/confirm.component.html +++ b/client/src/app/modal/confirm.component.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <div class="modal-body" > | 9 | <div class="modal-body" > |
10 | <div [innerHtml]="message"></div> | 10 | <div [innerHtml]="message"></div> |
11 | 11 | ||
12 | <div *ngIf="inputLabel && expectedInputValue" class="form-group"> | 12 | <div *ngIf="inputLabel && expectedInputValue" class="form-group mt-3"> |
13 | <label for="confirmInput">{{ inputLabel }}</label> | 13 | <label for="confirmInput">{{ inputLabel }}</label> |
14 | <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" /> | 14 | <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" /> |
15 | </div> | 15 | </div> |
diff --git a/client/src/app/modal/confirm.component.scss b/client/src/app/modal/confirm.component.scss index 77ea4d307..3372baf1a 100644 --- a/client/src/app/modal/confirm.component.scss +++ b/client/src/app/modal/confirm.component.scss | |||
@@ -1,10 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .button { | 4 | .button { |
9 | padding: 0 13px; | 5 | padding: 0 13px; |
10 | } | 6 | } |
@@ -13,7 +9,3 @@ input[type=text] { | |||
13 | @include peertube-input-text(100%); | 9 | @include peertube-input-text(100%); |
14 | display: block; | 10 | display: block; |
15 | } | 11 | } |
16 | |||
17 | .form-group { | ||
18 | margin: 20px 0; | ||
19 | } | ||
diff --git a/client/src/app/modal/custom-modal.component.scss b/client/src/app/modal/custom-modal.component.scss index 15cbadfc0..c5b77994e 100644 --- a/client/src/app/modal/custom-modal.component.scss +++ b/client/src/app/modal/custom-modal.component.scss | |||
@@ -1,10 +1,6 @@ | |||
1 | @use '_mixins' as *; | 1 | @use '_mixins' as *; |
2 | @use '_variables' as *; | 2 | @use '_variables' as *; |
3 | 3 | ||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | li { | 4 | li { |
9 | margin-bottom: 10px; | 5 | margin-bottom: 10px; |
10 | } | 6 | } |
diff --git a/client/src/app/modal/instance-config-warning-modal.component.scss b/client/src/app/modal/instance-config-warning-modal.component.scss index 3e6c6d716..b650f4e69 100644 --- a/client/src/app/modal/instance-config-warning-modal.component.scss +++ b/client/src/app/modal/instance-config-warning-modal.component.scss | |||
@@ -1,10 +1,6 @@ | |||
1 | @use '_mixins' as *; | 1 | @use '_mixins' as *; |
2 | @use '_variables' as *; | 2 | @use '_variables' as *; |
3 | 3 | ||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | li { | 4 | li { |
9 | margin-bottom: 10px; | 5 | margin-bottom: 10px; |
10 | } | 6 | } |
diff --git a/client/src/app/modal/quick-settings-modal.component.html b/client/src/app/modal/quick-settings-modal.component.html index 8fa3aee50..5c55ee028 100644 --- a/client/src/app/modal/quick-settings-modal.component.html +++ b/client/src/app/modal/quick-settings-modal.component.html | |||
@@ -5,21 +5,17 @@ | |||
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
8 | <div i18n class="mb-4 font-italic">These settings apply only to your session on this instance.</div> | 8 | <div i18n class="alert pt-alert-primary">These settings apply only to your session on this instance.</div> |
9 | 9 | ||
10 | <h6 i18n class="mb-4">Display settings</h6> | 10 | <h5 i18n class="mt-4 mb-2">Videos</h5> |
11 | 11 | ||
12 | <my-user-video-settings | 12 | <my-user-video-settings |
13 | *ngIf="!isUserLoggedIn()" | 13 | *ngIf="!isUserLoggedIn()" |
14 | [user]="user" [userInformationLoaded]="userInformationLoaded" [reactiveUpdate]="true" [notifyOnUpdate]="true" | 14 | [user]="user" [userInformationLoaded]="userInformationLoaded" [reactiveUpdate]="true" [notifyOnUpdate]="true" |
15 | > | 15 | > |
16 | |||
17 | <ng-container ngProjectAs="inner-title"> | ||
18 | <h6 i18n class="mb-4 mt-4">Video settings</h6> | ||
19 | </ng-container> | ||
20 | </my-user-video-settings> | 16 | </my-user-video-settings> |
21 | 17 | ||
22 | <h6 i18n class="mb-4 mt-4">Interface settings</h6> | 18 | <h5 i18n class="mt-4 mb-2">Interface</h5> |
23 | 19 | ||
24 | <my-user-interface-settings | 20 | <my-user-interface-settings |
25 | *ngIf="!isUserLoggedIn()" | 21 | *ngIf="!isUserLoggedIn()" |
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts index 6d0dea64e..3262853d8 100644 --- a/client/src/app/shared/form-validators/user-validators.ts +++ b/client/src/app/shared/form-validators/user-validators.ts | |||
@@ -61,7 +61,7 @@ export const USER_EXISTING_PASSWORD_VALIDATOR: BuildFormValidator = { | |||
61 | } | 61 | } |
62 | } | 62 | } |
63 | 63 | ||
64 | export const USER_PASSWORD_VALIDATOR: BuildFormValidator = { | 64 | export const USER_PASSWORD_VALIDATOR = { |
65 | VALIDATORS: [ | 65 | VALIDATORS: [ |
66 | Validators.required, | 66 | Validators.required, |
67 | Validators.minLength(6), | 67 | Validators.minLength(6), |
diff --git a/client/src/app/shared/form-validators/video-channel-validators.ts b/client/src/app/shared/form-validators/video-channel-validators.ts index 48f5b1a2c..163faf270 100644 --- a/client/src/app/shared/form-validators/video-channel-validators.ts +++ b/client/src/app/shared/form-validators/video-channel-validators.ts | |||
@@ -45,6 +45,6 @@ export const VIDEO_CHANNEL_SUPPORT_VALIDATOR: BuildFormValidator = { | |||
45 | ], | 45 | ], |
46 | MESSAGES: { | 46 | MESSAGES: { |
47 | minlength: $localize`Support text must be at least 3 characters long.`, | 47 | minlength: $localize`Support text must be at least 3 characters long.`, |
48 | maxlength: $localize`Support text cannot be more than 1000 characters long` | 48 | maxlength: $localize`Support text cannot be more than 1000 characters long.` |
49 | } | 49 | } |
50 | } | 50 | } |
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.html b/client/src/app/shared/shared-abuse-list/abuse-details.component.html index 986de15ed..fdf700f7c 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-details.component.html +++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.html | |||
@@ -10,16 +10,17 @@ | |||
10 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" | 10 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" |
11 | class="chip" | 11 | class="chip" |
12 | > | 12 | > |
13 | <my-actor-avatar size="18" [account]="abuse.reporterAccount"></my-actor-avatar> | 13 | <my-actor-avatar size="18" [actor]="abuse.reporterAccount" actorType="account"></my-actor-avatar> |
14 | <div> | 14 | <div> |
15 | <span class="muted">{{ abuse.reporterAccount.nameWithHost }}</span> | 15 | <span class="muted">{{ abuse.reporterAccount.nameWithHost }}</span> |
16 | </div> | 16 | </div> |
17 | </a> | 17 | </a> |
18 | 18 | ||
19 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" | 19 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" |
20 | class="ml-auto muted abuse-details-links" i18n | 20 | class="ms-auto muted abuse-details-links" i18n |
21 | > | 21 | > |
22 | {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> | 22 | {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}} |
23 | <my-global-icon iconName="flag"></my-global-icon> | ||
23 | </a> | 24 | </a> |
24 | </span> | 25 | </span> |
25 | </div> | 26 | </div> |
@@ -30,16 +31,17 @@ | |||
30 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" | 31 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" |
31 | class="chip" | 32 | class="chip" |
32 | > | 33 | > |
33 | <my-actor-avatar size="18" [account]="abuse.flaggedAccount"></my-actor-avatar> | 34 | <my-actor-avatar size="18" [actor]="abuse.flaggedAccount" actorType="account"></my-actor-avatar> |
34 | <div> | 35 | <div> |
35 | <span class="muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span> | 36 | <span class="muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span> |
36 | </div> | 37 | </div> |
37 | </a> | 38 | </a> |
38 | 39 | ||
39 | <a *ngIf="isAdminView" [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" | 40 | <a *ngIf="isAdminView" [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" |
40 | class="ml-auto muted abuse-details-links" i18n | 41 | class="ms-auto muted abuse-details-links" i18n |
41 | > | 42 | > |
42 | {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> | 43 | {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}} |
44 | <my-global-icon iconName="flag"></my-global-icon> | ||
43 | </a> | 45 | </a> |
44 | </span> | 46 | </span> |
45 | </div> | 47 | </div> |
@@ -53,7 +55,7 @@ | |||
53 | <div class="mt-3 d-flex"> | 55 | <div class="mt-3 d-flex"> |
54 | <span class="moderation-expanded-label"> | 56 | <span class="moderation-expanded-label"> |
55 | <ng-container i18n>Report</ng-container> | 57 | <ng-container i18n>Report</ng-container> |
56 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 muted">#{{ abuse.id }}</a> | 58 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ms-1 muted">#{{ abuse.id }}</a> |
57 | </span> | 59 | </span> |
58 | <span class="moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span> | 60 | <span class="moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span> |
59 | </div> | 61 | </div> |
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.scss b/client/src/app/shared/shared-abuse-list/abuse-details.component.scss index 37bf4cc56..bd43ed459 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-details.component.scss +++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.scss | |||
@@ -15,3 +15,7 @@ | |||
15 | .abuse-details-links { | 15 | .abuse-details-links { |
16 | @include disable-default-a-behaviour; | 16 | @include disable-default-a-behaviour; |
17 | } | 17 | } |
18 | |||
19 | my-global-icon[iconName=flag] { | ||
20 | width: 15px; | ||
21 | } | ||
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html index f0a27c6e2..5f9db2b3b 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html | |||
@@ -8,7 +8,7 @@ | |||
8 | > | 8 | > |
9 | <ng-template pTemplate="caption"> | 9 | <ng-template pTemplate="caption"> |
10 | <div class="caption"> | 10 | <div class="caption"> |
11 | <div class="ml-auto"> | 11 | <div class="ms-auto"> |
12 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> | 12 | <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> |
13 | </div> | 13 | </div> |
14 | </div> | 14 | </div> |
@@ -43,7 +43,7 @@ | |||
43 | <td *ngIf="isAdminView()"> | 43 | <td *ngIf="isAdminView()"> |
44 | <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> | 44 | <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> |
45 | <div class="chip two-lines"> | 45 | <div class="chip two-lines"> |
46 | <my-actor-avatar [account]="abuse.reporterAccount" size="32"></my-actor-avatar> | 46 | <my-actor-avatar [actor]="abuse.reporterAccount" actorType="account" size="32"></my-actor-avatar> |
47 | <div> | 47 | <div> |
48 | {{ abuse.reporterAccount.displayName }} | 48 | {{ abuse.reporterAccount.displayName }} |
49 | <span>{{ abuse.reporterAccount.nameWithHost }}</span> | 49 | <span>{{ abuse.reporterAccount.nameWithHost }}</span> |
@@ -70,7 +70,7 @@ | |||
70 | </span> | 70 | </span> |
71 | 71 | ||
72 | <span name> | 72 | <span name> |
73 | <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span> | 73 | <my-global-icon *ngIf="abuse.video.blacklisted" iconName="no" i18n-title title="The video was blocked"></my-global-icon> |
74 | </span> | 74 | </span> |
75 | </my-video-cell> | 75 | </my-video-cell> |
76 | </td> | 76 | </td> |
@@ -80,7 +80,7 @@ | |||
80 | <div class="table-video-text"> | 80 | <div class="table-video-text"> |
81 | <div> | 81 | <div> |
82 | {{ abuse.video.name }} | 82 | {{ abuse.video.name }} |
83 | <span class="glyphicon glyphicon-trash"></span> | 83 | <my-global-icon iconName="delete"></my-global-icon> |
84 | </div> | 84 | </div> |
85 | <div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> | 85 | <div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> |
86 | </div> | 86 | </div> |
@@ -116,8 +116,8 @@ | |||
116 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> | 116 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> |
117 | 117 | ||
118 | <td class="c-hand abuse-states" [pRowToggler]="abuse"> | 118 | <td class="c-hand abuse-states" [pRowToggler]="abuse"> |
119 | <span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span> | 119 | <my-global-icon *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" iconName="tick"></my-global-icon> |
120 | <span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span> | 120 | <my-global-icon *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" iconName="cross"></my-global-icon> |
121 | </td> | 121 | </td> |
122 | 122 | ||
123 | <td class="c-hand abuse-messages" (click)="openAbuseMessagesModal(abuse)"> | 123 | <td class="c-hand abuse-messages" (click)="openAbuseMessagesModal(abuse)"> |
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.scss b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.scss index 2d8acae58..4852f2e8b 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.scss +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.scss | |||
@@ -7,10 +7,6 @@ | |||
7 | color: var(--greyForegroundColor); | 7 | color: var(--greyForegroundColor); |
8 | } | 8 | } |
9 | 9 | ||
10 | .abuse-states .glyphicon-comment { | ||
11 | @include margin-left(0.5rem); | ||
12 | } | ||
13 | |||
14 | .abuse-messages { | 10 | .abuse-messages { |
15 | my-global-icon { | 11 | my-global-icon { |
16 | @include margin-left(3px); | 12 | @include margin-left(3px); |
@@ -20,3 +16,10 @@ | |||
20 | top: -2px; | 16 | top: -2px; |
21 | } | 17 | } |
22 | } | 18 | } |
19 | |||
20 | .table-video-text my-global-icon, | ||
21 | my-video-cell my-global-icon { | ||
22 | width: 15px; | ||
23 | position: relative; | ||
24 | top: -2px; | ||
25 | } | ||
diff --git a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss index d77e44a9d..3b43a4a4d 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss +++ b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss | |||
@@ -25,7 +25,6 @@ textarea { | |||
25 | 25 | ||
26 | .no-messages { | 26 | .no-messages { |
27 | display: flex; | 27 | display: flex; |
28 | font-size: 15px; | ||
29 | justify-content: center; | 28 | justify-content: center; |
30 | } | 29 | } |
31 | 30 | ||
@@ -45,10 +44,6 @@ textarea { | |||
45 | color: var(--mainForegroundColor); | 44 | color: var(--mainForegroundColor); |
46 | background-color: var(--greyBackgroundColor); | 45 | background-color: var(--greyBackgroundColor); |
47 | 46 | ||
48 | .content { | ||
49 | font-size: 15px; | ||
50 | } | ||
51 | |||
52 | .date { | 47 | .date { |
53 | font-size: 13px; | 48 | font-size: 13px; |
54 | color: var(--greyForegroundColor); | 49 | color: var(--greyForegroundColor); |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html index e9c5fadcf..6459c5ffe 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html | |||
@@ -1,12 +1,12 @@ | |||
1 | <div class="actor" *ngIf="actor"> | 1 | <div class="actor" *ngIf="actor"> |
2 | <div class="d-flex"> | 2 | <div class="d-flex"> |
3 | <my-actor-avatar [channel]="getChannel()" [account]="getAccount()" [previewImage]="preview" size="100"></my-actor-avatar> | 3 | <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar> |
4 | 4 | ||
5 | <div class="actor-img-edit-container"> | 5 | <div class="actor-img-edit-container"> |
6 | 6 | ||
7 | <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> | 7 | <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> |
8 | <my-global-icon iconName="upload"></my-global-icon> | 8 | <my-global-icon iconName="upload"></my-global-icon> |
9 | <label class="sr-only" for="avatarfile" i18n>Upload a new avatar</label> | 9 | <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label> |
10 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | 10 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> |
11 | </div> | 11 | </div> |
12 | 12 | ||
@@ -15,7 +15,7 @@ | |||
15 | #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" | 15 | #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" |
16 | > | 16 | > |
17 | <my-global-icon iconName="edit"></my-global-icon> | 17 | <my-global-icon iconName="edit"></my-global-icon> |
18 | <label class="sr-only" for="avatarMenu" i18n>Change your avatar</label> | 18 | <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label> |
19 | </div> | 19 | </div> |
20 | 20 | ||
21 | </div> | 21 | </div> |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss index 923f0029b..fd8cd7ffc 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss | |||
@@ -3,37 +3,36 @@ | |||
3 | 3 | ||
4 | .actor { | 4 | .actor { |
5 | display: flex; | 5 | display: flex; |
6 | } | ||
6 | 7 | ||
7 | my-actor-avatar { | 8 | my-actor-avatar { |
8 | @include margin-right(15px); | 9 | @include margin-right(15px); |
9 | } | 10 | } |
10 | 11 | ||
11 | .actor-info { | 12 | .actor-info { |
12 | display: inline-flex; | 13 | display: inline-flex; |
13 | flex-direction: column; | 14 | flex-direction: column; |
15 | } | ||
14 | 16 | ||
15 | .actor-info-display-name { | 17 | .actor-info-display-name { |
16 | @include peertube-word-wrap; | 18 | @include peertube-word-wrap; |
17 | 19 | ||
18 | font-size: 20px; | 20 | font-size: 20px; |
19 | font-weight: $font-bold; | 21 | font-weight: $font-bold; |
20 | 22 | ||
21 | @media screen and (max-width: $small-view) { | 23 | @media screen and (max-width: $small-view) { |
22 | font-size: 16px; | 24 | font-size: 16px; |
23 | } | 25 | } |
24 | } | 26 | } |
25 | 27 | ||
26 | .actor-info-username { | 28 | .actor-info-username { |
27 | position: relative; | 29 | position: relative; |
28 | font-size: 14px; | 30 | font-size: 14px; |
29 | color: pvar(--greyForegroundColor); | 31 | color: pvar(--greyForegroundColor); |
30 | } | 32 | } |
31 | 33 | ||
32 | .actor-info-followers { | 34 | .actor-info-followers { |
33 | font-size: 15px; | 35 | padding-bottom: .5rem; |
34 | padding-bottom: .5rem; | ||
35 | } | ||
36 | } | ||
37 | } | 36 | } |
38 | 37 | ||
39 | .actor-img-edit-container { | 38 | .actor-img-edit-container { |
@@ -46,3 +45,7 @@ | |||
46 | right: 45px; | 45 | right: 45px; |
47 | border-radius: 50%; | 46 | border-radius: 50%; |
48 | } | 47 | } |
48 | |||
49 | .dropdown-item { | ||
50 | @include dropdown-with-icon-item; | ||
51 | } | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts index 01bb401fb..b71a3c485 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts | |||
@@ -75,19 +75,9 @@ export class ActorAvatarEditComponent implements OnInit { | |||
75 | return !!this.preview || this.actor.avatars.length !== 0 | 75 | return !!this.preview || this.actor.avatars.length !== 0 |
76 | } | 76 | } |
77 | 77 | ||
78 | isChannel () { | 78 | getActorType () { |
79 | return !!(this.actor as VideoChannel).ownerAccount | 79 | if ((this.actor as VideoChannel).ownerAccount) return 'channel' |
80 | } | ||
81 | |||
82 | getChannel (): VideoChannel { | ||
83 | if (this.isChannel()) return this.actor as VideoChannel | ||
84 | |||
85 | return undefined | ||
86 | } | ||
87 | |||
88 | getAccount (): Account { | ||
89 | if (this.isChannel()) return undefined | ||
90 | 80 | ||
91 | return this.actor as Account | 81 | return 'account' |
92 | } | 82 | } |
93 | } | 83 | } |
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.html b/client/src/app/shared/shared-actor-image/actor-avatar.component.html index c285b6cc3..fb9efc20a 100644 --- a/client/src/app/shared/shared-actor-image/actor-avatar.component.html +++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.html | |||
@@ -1,19 +1,21 @@ | |||
1 | <ng-template #img> | 1 | <ng-template #img> |
2 | <img *ngIf="previewImage || avatarUrl || !initial" [class]="getClass('avatar')" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" /> | 2 | <img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" /> |
3 | 3 | ||
4 | <div *ngIf="!avatarUrl && initial" [ngClass]="getClass('initial')"> | 4 | <div *ngIf="displayActorInitial()" [ngClass]="classes"> |
5 | <span>{{ initial }}</span> | 5 | <span>{{ getActorInitial() }}</span> |
6 | </div> | 6 | </div> |
7 | |||
8 | <div *ngIf="displayPlaceholder()" [ngClass]="classes"></div> | ||
7 | </ng-template> | 9 | </ng-template> |
8 | 10 | ||
9 | <a *ngIf="hasActor() && href" [href]="href" target="_blank" rel="noopener noreferrer" [title]="title"> | 11 | <a *ngIf="actor && href" [href]="href" target="_blank" rel="noopener noreferrer" [title]="title"> |
10 | <ng-template *ngTemplateOutlet="img"></ng-template> | 12 | <ng-template *ngTemplateOutlet="img"></ng-template> |
11 | </a> | 13 | </a> |
12 | 14 | ||
13 | <a *ngIf="hasActor() && internalHref" [routerLink]="internalHref" [title]="title"> | 15 | <a *ngIf="actor && internalHref" [routerLink]="internalHref" [title]="title"> |
14 | <ng-template *ngTemplateOutlet="img"></ng-template> | 16 | <ng-template *ngTemplateOutlet="img"></ng-template> |
15 | </a> | 17 | </a> |
16 | 18 | ||
17 | <ng-container *ngIf="!hasActor() || (!href && !internalHref)"> | 19 | <ng-container *ngIf="!actor || (!href && !internalHref)"> |
18 | <ng-template *ngTemplateOutlet="img"></ng-template> | 20 | <ng-template *ngTemplateOutlet="img"></ng-template> |
19 | </ng-container> | 21 | </ng-container> |
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts index 600984aa2..437de1d50 100644 --- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts +++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input, OnChanges } from '@angular/core' |
2 | import { VideoChannel } from '../shared-main' | 2 | import { VideoChannel } from '../shared-main' |
3 | import { Account } from '../shared-main/account/account.model' | 3 | import { Account } from '../shared-main/account/account.model' |
4 | 4 | ||
@@ -15,11 +15,11 @@ export type ActorAvatarSize = '18' | '25' | '28' | '32' | '34' | '35' | '36' | ' | |||
15 | styleUrls: [ './actor-avatar.component.scss' ], | 15 | styleUrls: [ './actor-avatar.component.scss' ], |
16 | templateUrl: './actor-avatar.component.html' | 16 | templateUrl: './actor-avatar.component.html' |
17 | }) | 17 | }) |
18 | export class ActorAvatarComponent { | 18 | export class ActorAvatarComponent implements OnChanges { |
19 | private _title: string | 19 | private _title: string |
20 | 20 | ||
21 | @Input() account: ActorInput | 21 | @Input() actor: ActorInput |
22 | @Input() channel: ActorInput | 22 | @Input() actorType: 'channel' | 'account' | 'unlogged' |
23 | 23 | ||
24 | @Input() previewImage: string | 24 | @Input() previewImage: string |
25 | 25 | ||
@@ -36,56 +36,84 @@ export class ActorAvatarComponent { | |||
36 | 36 | ||
37 | get title () { | 37 | get title () { |
38 | if (this._title) return this._title | 38 | if (this._title) return this._title |
39 | if (this.account) return $localize`${this.account.name} (account page)` | 39 | if (this.isAccount()) return $localize`${this.actor.name} (account page)` |
40 | if (this.channel) return $localize`${this.channel.name} (channel page)` | 40 | if (this.isChannel()) return $localize`${this.actor.name} (channel page)` |
41 | 41 | ||
42 | return '' | 42 | return '' |
43 | } | 43 | } |
44 | 44 | ||
45 | classes: string[] = [] | ||
46 | |||
45 | get alt () { | 47 | get alt () { |
46 | if (this.account) return $localize`Account avatar` | 48 | if (this.isAccount()) return $localize`Account avatar` |
47 | if (this.channel) return $localize`Channel avatar` | 49 | if (this.isChannel()) return $localize`Channel avatar` |
48 | 50 | ||
49 | return '' | 51 | return '' |
50 | } | 52 | } |
51 | 53 | ||
52 | get defaultAvatarUrl () { | 54 | get defaultAvatarUrl () { |
53 | if (this.account) return Account.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) | 55 | if (this.isChannel()) return VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) |
54 | if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) | 56 | |
57 | // account or unlogged | ||
58 | return Account.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) | ||
55 | } | 59 | } |
56 | 60 | ||
57 | get avatarUrl () { | 61 | get avatarUrl () { |
58 | if (this.account) return Account.GET_ACTOR_AVATAR_URL(this.account, this.getSizeNumber()) | 62 | if (!this.actor) return '' |
59 | if (this.channel) return VideoChannel.GET_ACTOR_AVATAR_URL(this.channel, this.getSizeNumber()) | 63 | |
64 | if (this.isAccount()) return Account.GET_ACTOR_AVATAR_URL(this.actor, this.getSizeNumber()) | ||
65 | if (this.isChannel()) return VideoChannel.GET_ACTOR_AVATAR_URL(this.actor, this.getSizeNumber()) | ||
60 | 66 | ||
61 | return '' | 67 | return '' |
62 | } | 68 | } |
63 | 69 | ||
64 | get initial () { | 70 | ngOnChanges () { |
65 | const name = this.account?.name | 71 | this.classes = [ 'avatar' ] |
66 | if (!name) return '' | ||
67 | 72 | ||
68 | return name.slice(0, 1) | 73 | if (this.size) { |
74 | this.classes.push(`avatar-${this.size}`) | ||
75 | } | ||
76 | |||
77 | if (this.isChannel()) { | ||
78 | this.classes.push('channel') | ||
79 | } else { | ||
80 | this.classes.push('account') | ||
81 | } | ||
82 | |||
83 | // No avatar, use actor name initial | ||
84 | if (this.displayActorInitial()) { | ||
85 | this.classes.push('initial') | ||
86 | this.classes.push(this.getColorTheme()) | ||
87 | } | ||
69 | } | 88 | } |
70 | 89 | ||
71 | getClass (type: 'avatar' | 'initial') { | 90 | displayImage () { |
72 | const base = [ 'avatar' ] | 91 | if (this.actorType === 'unlogged') return true |
73 | 92 | ||
74 | if (this.size) base.push(`avatar-${this.size}`) | 93 | return !!(this.actor && this.avatarUrl) |
94 | } | ||
75 | 95 | ||
76 | if (this.channel) base.push('channel') | 96 | displayActorInitial () { |
77 | else base.push('account') | 97 | return this.actor && !this.avatarUrl |
98 | } | ||
78 | 99 | ||
79 | if (type === 'initial' && this.initial) { | 100 | displayPlaceholder () { |
80 | base.push('initial') | 101 | return this.actorType !== 'unlogged' && !this.actor |
81 | base.push(this.getColorTheme()) | 102 | } |
82 | } | 103 | |
104 | getActorInitial () { | ||
105 | const name = this.actor?.name | ||
106 | if (!name) return '' | ||
107 | |||
108 | return name.slice(0, 1) | ||
109 | } | ||
83 | 110 | ||
84 | return base | 111 | private isAccount () { |
112 | return this.actorType === 'account' | ||
85 | } | 113 | } |
86 | 114 | ||
87 | hasActor () { | 115 | private isChannel () { |
88 | return !!this.account || !!this.channel | 116 | return this.actorType === 'channel' |
89 | } | 117 | } |
90 | 118 | ||
91 | private getSizeNumber () { | 119 | private getSizeNumber () { |
@@ -95,7 +123,7 @@ export class ActorAvatarComponent { | |||
95 | } | 123 | } |
96 | 124 | ||
97 | private getColorTheme () { | 125 | private getColorTheme () { |
98 | const initialLowercase = this.initial.toLowerCase() | 126 | const initialLowercase = this.getActorInitial().toLowerCase() |
99 | 127 | ||
100 | // Keep consistency with CSS | 128 | // Keep consistency with CSS |
101 | const themes = { | 129 | const themes = { |
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html index 52a402329..395cc8562 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div *ngIf="channel" class="channel"> | 1 | <div *ngIf="channel" class="channel"> |
2 | 2 | ||
3 | <div class="channel-avatar-row"> | 3 | <div class="channel-avatar-row"> |
4 | <my-actor-avatar [channel]="channel" [internalHref]="getVideoChannelLink()" i18n-title title="See this video channel" size="75"></my-actor-avatar> | 4 | <my-actor-avatar [actor]="channel" actorType="channel" [internalHref]="getVideoChannelLink()" i18n-title title="See this video channel" size="75"></my-actor-avatar> |
5 | 5 | ||
6 | <h6> | 6 | <h6> |
7 | <a [routerLink]="getVideoChannelLink()" i18n-title title="See this video channel"> | 7 | <a [routerLink]="getVideoChannelLink()" i18n-title title="See this video channel"> |
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts index 0e4d5fb12..7d3498d4c 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts | |||
@@ -7,7 +7,7 @@ import { MiniatureDisplayOptions } from '../../shared-video-miniature' | |||
7 | import { CustomMarkupComponent } from './shared' | 7 | import { CustomMarkupComponent } from './shared' |
8 | 8 | ||
9 | /* | 9 | /* |
10 | * Markup component list videos depending on criterias | 10 | * Markup component list videos depending on criteria |
11 | */ | 11 | */ |
12 | 12 | ||
13 | @Component({ | 13 | @Component({ |
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.html b/client/src/app/shared/shared-forms/advanced-input-filter.component.html index 7031cb53b..e4f318385 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.html +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.html | |||
@@ -1,6 +1,7 @@ | |||
1 | <div class="input-group has-feedback has-clear"> | 1 | <div class="input-group has-clear" ngbDropdown placement="bottom-left auto" container="body"> |
2 | <div *ngIf="hasFilters()" class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body"> | 2 | |
3 | <div class="input-group-text" ngbDropdownToggle> | 3 | <ng-container *ngIf="hasFilters()"> |
4 | <div class="input-group-text c-hand" ngbDropdownToggle> | ||
4 | <span class="caret" aria-haspopup="menu" role="button"></span> | 5 | <span class="caret" aria-haspopup="menu" role="button"></span> |
5 | </div> | 6 | </div> |
6 | 7 | ||
@@ -15,14 +16,14 @@ | |||
15 | </button> | 16 | </button> |
16 | </ng-container> | 17 | </ng-container> |
17 | </div> | 18 | </div> |
18 | </div> | 19 | </ng-container> |
19 | 20 | ||
20 | <input | 21 | <input |
21 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 22 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
23 | class="last-in-group" | ||
22 | [(ngModel)]="searchValue" | 24 | [(ngModel)]="searchValue" |
23 | (keyup)="onInputSearch($event)" | 25 | (keyup)="onInputSearch($event)" |
24 | > | 26 | > |
25 | 27 | ||
26 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="onResetTableFilter()"></a> | 28 | <my-global-icon iconName="cross" role="button" class="form-control-clear" title="Clear filter" i18n-title (click)="onResetTableFilter()"></my-global-icon> |
27 | <span class="sr-only" i18n>Clear filters</span> | ||
28 | </div> | 29 | </div> |
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss index ee1b3b508..ed85385fd 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss | |||
@@ -23,7 +23,6 @@ my-global-icon { | |||
23 | width: 18px; | 23 | width: 18px; |
24 | height: 18px; | 24 | height: 18px; |
25 | 25 | ||
26 | |||
27 | &.no-visible { | 26 | &.no-visible { |
28 | @include margin-right($size + $margin); | 27 | @include margin-right($size + $margin); |
29 | 28 | ||
diff --git a/client/src/app/shared/shared-forms/dynamic-form-field.component.html b/client/src/app/shared/shared-forms/dynamic-form-field.component.html index 2ef61ecfc..2dd6cf4ad 100644 --- a/client/src/app/shared/shared-forms/dynamic-form-field.component.html +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.html | |||
@@ -18,7 +18,7 @@ | |||
18 | </select> | 18 | </select> |
19 | </div> | 19 | </div> |
20 | 20 | ||
21 | <my-input-toggle-hidden *ngIf="setting.type === 'input-password'" [formControlName]="setting.name" [inputId]="setting.name"></my-input-toggle-hidden> | 21 | <my-input-text *ngIf="setting.type === 'input-password'" [formError]="formErrors['settings.name']" [formControlName]="setting.name" [inputId]="setting.name"></my-input-text> |
22 | 22 | ||
23 | <textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea> | 23 | <textarea *ngIf="setting.type === 'input-textarea'" type="text" [id]="setting.name" [formControlName]="setting.name"></textarea> |
24 | 24 | ||
@@ -28,19 +28,19 @@ | |||
28 | 28 | ||
29 | <my-markdown-textarea | 29 | <my-markdown-textarea |
30 | *ngIf="setting.type === 'markdown-text'" | 30 | *ngIf="setting.type === 'markdown-text'" |
31 | markdownType="text" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | 31 | markdownType="text" [id]="setting.name" [formControlName]="setting.name" |
32 | [classes]="{ 'input-error': formErrors['settings.name'] }" | 32 | [formError]="formErrors['settings.name']" |
33 | ></my-markdown-textarea> | 33 | ></my-markdown-textarea> |
34 | 34 | ||
35 | <my-markdown-textarea | 35 | <my-markdown-textarea |
36 | *ngIf="setting.type === 'markdown-enhanced'" | 36 | *ngIf="setting.type === 'markdown-enhanced'" |
37 | markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" textareaWidth="500px" | 37 | markdownType="enhanced" [id]="setting.name" [formControlName]="setting.name" |
38 | [classes]="{ 'input-error': formErrors['settings.name'] }" | 38 | [formError]="formErrors['settings.name']" |
39 | ></my-markdown-textarea> | 39 | ></my-markdown-textarea> |
40 | 40 | ||
41 | <div *ngIf="setting.type === 'html'" [innerHTML]="setting.html"></div> | 41 | <div *ngIf="setting.type === 'html'" [innerHTML]="setting.html"></div> |
42 | 42 | ||
43 | <div *ngIf="formErrors[setting.name]" class="form-error"> | 43 | <div *ngIf="hasDedicatedFormError() && formErrors[setting.name]" class="form-error"> |
44 | {{ formErrors[setting.name] }} | 44 | {{ formErrors[setting.name] }} |
45 | </div> | 45 | </div> |
46 | 46 | ||
diff --git a/client/src/app/shared/shared-forms/dynamic-form-field.component.scss b/client/src/app/shared/shared-forms/dynamic-form-field.component.scss index 31bf59edb..0737f372a 100644 --- a/client/src/app/shared/shared-forms/dynamic-form-field.component.scss +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.scss | |||
@@ -26,3 +26,8 @@ textarea { | |||
26 | my-peertube-checkbox + .label-small-info { | 26 | my-peertube-checkbox + .label-small-info { |
27 | margin-top: 5px; | 27 | margin-top: 5px; |
28 | } | 28 | } |
29 | |||
30 | my-markdown-textarea { | ||
31 | display: block; | ||
32 | max-width: 500px; | ||
33 | } | ||
diff --git a/client/src/app/shared/shared-forms/dynamic-form-field.component.ts b/client/src/app/shared/shared-forms/dynamic-form-field.component.ts index b63890797..e1a1f8034 100644 --- a/client/src/app/shared/shared-forms/dynamic-form-field.component.ts +++ b/client/src/app/shared/shared-forms/dynamic-form-field.component.ts | |||
@@ -12,4 +12,15 @@ export class DynamicFormFieldComponent { | |||
12 | @Input() form: FormGroup | 12 | @Input() form: FormGroup |
13 | @Input() formErrors: any | 13 | @Input() formErrors: any |
14 | @Input() setting: RegisterClientFormFieldOptions | 14 | @Input() setting: RegisterClientFormFieldOptions |
15 | |||
16 | hasDedicatedFormError () { | ||
17 | const dedicated = new Set<RegisterClientFormFieldOptions['type']>([ | ||
18 | 'input-checkbox', | ||
19 | 'input', | ||
20 | 'select', | ||
21 | 'input-textarea' | ||
22 | ]) | ||
23 | |||
24 | return dedicated.has(this.setting.type) | ||
25 | } | ||
15 | } | 26 | } |
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts index 727416a40..495785e7b 100644 --- a/client/src/app/shared/shared-forms/index.ts +++ b/client/src/app/shared/shared-forms/index.ts | |||
@@ -3,7 +3,7 @@ export * from './form-reactive' | |||
3 | export * from './form-validator.service' | 3 | export * from './form-validator.service' |
4 | export * from './form-validator.service' | 4 | export * from './form-validator.service' |
5 | export * from './input-switch.component' | 5 | export * from './input-switch.component' |
6 | export * from './input-toggle-hidden.component' | 6 | export * from './input-text.component' |
7 | export * from './markdown-textarea.component' | 7 | export * from './markdown-textarea.component' |
8 | export * from './peertube-checkbox.component' | 8 | export * from './peertube-checkbox.component' |
9 | export * from './preview-upload.component' | 9 | export * from './preview-upload.component' |
diff --git a/client/src/app/shared/shared-forms/input-switch.component.html b/client/src/app/shared/shared-forms/input-switch.component.html index ce1dee470..9a466055a 100644 --- a/client/src/app/shared/shared-forms/input-switch.component.html +++ b/client/src/app/shared/shared-forms/input-switch.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div (click)="update()"> | 1 | <div (click)="update()"> |
2 | <input type="checkbox" [checked]="checked"/> | 2 | <input type="checkbox" [checked]="checked"/> |
3 | <label class="ml-auto">Toggle</label> | 3 | <label class="ms-auto">Toggle</label> |
4 | </div> | 4 | </div> |
diff --git a/client/src/app/shared/shared-forms/input-text.component.html b/client/src/app/shared/shared-forms/input-text.component.html new file mode 100644 index 000000000..abb53a085 --- /dev/null +++ b/client/src/app/shared/shared-forms/input-text.component.html | |||
@@ -0,0 +1,23 @@ | |||
1 | <div class="input-group"> | ||
2 | <input | ||
3 | [id]="inputId" [autocomplete]="autocomplete" [value]="value" [placeholder]="placeholder" [tabindex]="tabindex" | ||
4 | [(ngModel)]="value" (ngModelChange)="update()" [readonly]="readonly" | ||
5 | #input (click)="input.select()" (input)="update()" (change)="update()" [type]="inputType" class="form-control" | ||
6 | [ngClass]="{ 'input-error': formError }" | ||
7 | /> | ||
8 | |||
9 | <button *ngIf="withToggle" (click)="toggle()" type="button" class="btn btn-outline-secondary" [title]="toggleTitle"> | ||
10 | <my-global-icon *ngIf="show" iconName="eye-open"></my-global-icon> | ||
11 | <my-global-icon *ngIf="!show" iconName="eye-close"></my-global-icon> | ||
12 | </button> | ||
13 | |||
14 | <button | ||
15 | *ngIf="withCopy" [cdkCopyToClipboard]="input.value" (click)="activateCopiedMessage()" type="button" | ||
16 | class="btn btn-outline-secondary text-uppercase" i18n-title title="Copy" | ||
17 | > | ||
18 | <my-global-icon iconName="copy"></my-global-icon> | ||
19 | <span class="copy-text">Copy</span> | ||
20 | </button> | ||
21 | </div> | ||
22 | |||
23 | <div *ngIf="formError" class="form-error">{{ formError }}</div> | ||
diff --git a/client/src/app/shared/shared-forms/input-toggle-hidden.component.scss b/client/src/app/shared/shared-forms/input-text.component.scss index ef4236ebc..ae8bf5fec 100644 --- a/client/src/app/shared/shared-forms/input-toggle-hidden.component.scss +++ b/client/src/app/shared/shared-forms/input-text.component.scss | |||
@@ -5,11 +5,14 @@ input { | |||
5 | @include peertube-input-text(auto); | 5 | @include peertube-input-text(auto); |
6 | @include padding-left(15px !important); | 6 | @include padding-left(15px !important); |
7 | @include padding-right(15px !important); | 7 | @include padding-right(15px !important); |
8 | } | ||
8 | 9 | ||
9 | // set again properties of peertube-input-text that are overriden by .input-group | 10 | .btn { |
10 | font-size: 15px !important; | 11 | @include button-with-icon(18px); |
11 | } | 12 | } |
12 | 13 | ||
13 | .eye-button { | 14 | .copy-text { |
14 | line-height: 1 !important; | 15 | font-size: 14px; |
16 | margin-left: 5px; | ||
17 | vertical-align: top; | ||
15 | } | 18 | } |
diff --git a/client/src/app/shared/shared-forms/input-toggle-hidden.component.ts b/client/src/app/shared/shared-forms/input-text.component.ts index e03353fe1..d667ed663 100644 --- a/client/src/app/shared/shared-forms/input-toggle-hidden.component.ts +++ b/client/src/app/shared/shared-forms/input-text.component.ts | |||
@@ -3,18 +3,18 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | |||
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-input-toggle-hidden', | 6 | selector: 'my-input-text', |
7 | templateUrl: './input-toggle-hidden.component.html', | 7 | templateUrl: './input-text.component.html', |
8 | styleUrls: [ './input-toggle-hidden.component.scss' ], | 8 | styleUrls: [ './input-text.component.scss' ], |
9 | providers: [ | 9 | providers: [ |
10 | { | 10 | { |
11 | provide: NG_VALUE_ACCESSOR, | 11 | provide: NG_VALUE_ACCESSOR, |
12 | useExisting: forwardRef(() => InputToggleHiddenComponent), | 12 | useExisting: forwardRef(() => InputTextComponent), |
13 | multi: true | 13 | multi: true |
14 | } | 14 | } |
15 | ] | 15 | ] |
16 | }) | 16 | }) |
17 | export class InputToggleHiddenComponent implements ControlValueAccessor { | 17 | export class InputTextComponent implements ControlValueAccessor { |
18 | @Input() inputId = Math.random().toString(11).slice(2, 8) // id cannot be left empty or undefined | 18 | @Input() inputId = Math.random().toString(11).slice(2, 8) // id cannot be left empty or undefined |
19 | @Input() value = '' | 19 | @Input() value = '' |
20 | @Input() autocomplete = 'off' | 20 | @Input() autocomplete = 'off' |
@@ -24,6 +24,7 @@ export class InputToggleHiddenComponent implements ControlValueAccessor { | |||
24 | @Input() withCopy = false | 24 | @Input() withCopy = false |
25 | @Input() readonly = false | 25 | @Input() readonly = false |
26 | @Input() show = false | 26 | @Input() show = false |
27 | @Input() formError: string | ||
27 | 28 | ||
28 | constructor (private notifier: Notifier) { } | 29 | constructor (private notifier: Notifier) { } |
29 | 30 | ||
diff --git a/client/src/app/shared/shared-forms/input-toggle-hidden.component.html b/client/src/app/shared/shared-forms/input-toggle-hidden.component.html deleted file mode 100644 index dfe646d2f..000000000 --- a/client/src/app/shared/shared-forms/input-toggle-hidden.component.html +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | <div class="input-group input-group-sm"> | ||
2 | <input | ||
3 | [id]="inputId" [autocomplete]="autocomplete" [value]="value" [placeholder]="placeholder" [tabindex]="tabindex" | ||
4 | [(ngModel)]="value" (ngModelChange)="update()" [readonly]="readonly" | ||
5 | #input (click)="input.select()" (input)="update()" (change)="update()" [type]="inputType" class="form-control" | ||
6 | /> | ||
7 | |||
8 | <div *ngIf="withToggle || withCopy" class="input-group-append"> | ||
9 | <button *ngIf="withToggle" (click)="toggle()" type="button" class="btn btn-outline-secondary eye-button" [title]="toggleTitle"> | ||
10 | <span class="glyphicon glyphicon-eye-{{ show ? 'open' : 'close' }}"></span> | ||
11 | </button> | ||
12 | |||
13 | <button | ||
14 | *ngIf="withCopy" [cdkCopyToClipboard]="input.value" (click)="activateCopiedMessage()" type="button" | ||
15 | class="btn btn-outline-secondary text-uppercase" i18n-title title="Copy" | ||
16 | > | ||
17 | <span class="glyphicon glyphicon-duplicate"></span> | ||
18 | Copy | ||
19 | </button> | ||
20 | </div> | ||
21 | </div> | ||
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.html b/client/src/app/shared/shared-forms/markdown-textarea.component.html index 7c2a42791..5a9ff1a15 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.html +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.html | |||
@@ -1,9 +1,9 @@ | |||
1 | <div class="root" [ngClass]="{ 'maximized': isMaximized }" [ngStyle]="{ 'max-width': textareaMaxWidth }"> | 1 | <div class="root" [ngClass]="{ 'maximized': isMaximized }"> |
2 | |||
2 | <textarea #textarea | 3 | <textarea #textarea |
3 | [(ngModel)]="content" (ngModelChange)="onModelChange()" | 4 | [(ngModel)]="content" (ngModelChange)="onModelChange()" |
4 | class="form-control" [ngClass]="classes" | 5 | class="form-control" [ngClass]="{ 'input-error': formError }" |
5 | [attr.disabled]="disabled || null" | 6 | [attr.disabled]="disabled || null" |
6 | [ngStyle]="{ height: textareaHeight }" | ||
7 | [id]="name" [name]="name"> | 7 | [id]="name" [name]="name"> |
8 | </textarea> | 8 | </textarea> |
9 | 9 | ||
@@ -25,14 +25,18 @@ | |||
25 | </ng-template> | 25 | </ng-template> |
26 | </ng-container> | 26 | </ng-container> |
27 | 27 | ||
28 | <my-button | 28 | <my-global-icon |
29 | *ngIf="!isMaximized" [title]="maximizeInText" className="maximize-button" icon="fullscreen" (click)="onMaximizeClick()" [disabled]="disabled" | 29 | *ngIf="!isMaximized" role="button" [ngbTooltip]="maximizeInText" |
30 | ></my-button> | 30 | class="maximize-button" iconName="fullscreen" (click)="onMaximizeClick()" [ngClass]="{ disabled: disabled }" |
31 | ></my-global-icon> | ||
31 | 32 | ||
32 | <my-button | 33 | <my-global-icon |
33 | *ngIf="isMaximized" [title]="maximizeOutText" className="maximize-button" icon="exit-fullscreen" (click)="onMaximizeClick()" [disabled]="disabled" | 34 | *ngIf="isMaximized" role="button" [ngbTooltip]="maximizeOutText" |
34 | ></my-button> | 35 | class="maximize-button" iconName="exit-fullscreen" (click)="onMaximizeClick()" [ngClass]="{ disabled: disabled }" |
36 | ></my-global-icon> | ||
35 | </div> | 37 | </div> |
36 | 38 | ||
37 | <div [ngbNavOutlet]="nav"></div> | 39 | <div [ngbNavOutlet]="nav"></div> |
40 | |||
41 | <div *ngIf="!isMaximized && formError" class="form-error">{{ formError }}</div> | ||
38 | </div> | 42 | </div> |
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.scss b/client/src/app/shared/shared-forms/markdown-textarea.component.scss index 5939bb999..c68c2c241 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.scss +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.scss | |||
@@ -6,261 +6,169 @@ $nav-preview-tab-height: 30px; | |||
6 | $base-padding: 15px; | 6 | $base-padding: 15px; |
7 | $input-border-radius: 3px; | 7 | $input-border-radius: 3px; |
8 | 8 | ||
9 | @mixin in-small-view { | 9 | .root { |
10 | .root { | 10 | display: flex; |
11 | display: flex; | 11 | flex-direction: column; |
12 | flex-direction: column; | ||
13 | 12 | ||
14 | textarea { | 13 | textarea { |
15 | @include peertube-textarea(100%, 150px); | 14 | @include peertube-textarea(100%, 150px); |
16 | 15 | ||
17 | background-color: pvar(--markdownTextareaBackgroundColor); | 16 | background-color: pvar(--markdownTextareaBackgroundColor); |
18 | 17 | ||
19 | font-family: monospace; | 18 | font-family: monospace; |
20 | font-size: 13px; | 19 | font-size: 13px; |
21 | border-bottom: 0; | 20 | border-bottom: 0; |
22 | border-bottom-left-radius: unset; | 21 | border-bottom-left-radius: 0; |
23 | border-bottom-right-radius: unset; | 22 | border-bottom-right-radius: 0; |
24 | } | 23 | } |
25 | 24 | ||
26 | .nav-preview { | 25 | .nav-preview { |
27 | @include padding-left(10px); | 26 | padding: 10px; |
28 | @include padding-right(10px); | ||
29 | 27 | ||
30 | display: block; | 28 | border: 1px solid $input-border-color; |
31 | text-align: end; | 29 | border-top: 1px dashed $input-border-color; |
32 | padding-top: 10px; | ||
33 | padding-bottom: 10px; | ||
34 | border-top: 1px dashed $input-border-color; | ||
35 | border-left: 1px solid $input-border-color; | ||
36 | border-right: 1px solid $input-border-color; | ||
37 | border-bottom: 1px solid $input-border-color; | ||
38 | border-bottom-right-radius: $input-border-radius; | ||
39 | |||
40 | border-bottom-left-radius: $input-border-radius; | ||
41 | ::ng-deep { | ||
42 | .nav-link { | ||
43 | display: none !important; | ||
44 | } | ||
45 | 30 | ||
46 | .maximize-button { | 31 | border-bottom-right-radius: $input-border-radius; |
47 | padding: 0 7px; | 32 | border-bottom-left-radius: $input-border-radius; |
48 | cursor: pointer; | ||
49 | |||
50 | svg { | ||
51 | stroke: var(--mainForegroundColor); | ||
52 | opacity: 0.6; | ||
53 | } | ||
54 | |||
55 | &:hover, | ||
56 | &:active { | ||
57 | svg { | ||
58 | opacity: 1; | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | } | ||
64 | 33 | ||
65 | ::ng-deep { | 34 | display: flex; |
66 | .tab-content { | 35 | flex-grow: 1; |
67 | display: none; | 36 | border-bottom-left-radius: unset; |
68 | } | 37 | border-bottom-right-radius: unset; |
69 | } | 38 | border-bottom: 2px solid pvar(--mainColor); |
70 | } | ||
71 | } | ||
72 | 39 | ||
73 | @mixin nav-preview-medium { | 40 | .maximize-button { |
74 | display: flex; | 41 | @include margin-left(15px); |
75 | flex-grow: 1; | ||
76 | border-bottom-left-radius: unset; | ||
77 | border-bottom-right-radius: unset; | ||
78 | border-bottom: 2px solid pvar(--mainColor); | ||
79 | 42 | ||
80 | :first-child { | 43 | opacity: 0.6; |
81 | @include margin-left(auto); | 44 | cursor: default; |
82 | } | ||
83 | 45 | ||
84 | ::ng-deep { | 46 | &:not(.disabled) { |
85 | .nav-link { | 47 | cursor: pointer; |
86 | display: flex !important; | ||
87 | align-items: center; | ||
88 | height: $nav-preview-tab-height !important; | ||
89 | padding: 0 15px !important; | ||
90 | font-size: 85% !important; | ||
91 | opacity: .7; | ||
92 | } | ||
93 | 48 | ||
94 | .maximize-button { | 49 | &:hover, |
95 | @include margin-left(5px); | 50 | &:active { |
51 | opacity: 1; | ||
52 | } | ||
53 | } | ||
96 | } | 54 | } |
97 | } | 55 | } |
98 | } | ||
99 | 56 | ||
100 | @mixin content-preview-base { | 57 | .nav-pills { |
101 | display: block; | 58 | display: flex; |
102 | min-height: 75px; | 59 | align-items: center; |
103 | padding: $base-padding; | 60 | justify-content: flex-end; |
104 | overflow-y: auto; | ||
105 | font-size: 15px; | ||
106 | word-wrap: break-word; | ||
107 | } | ||
108 | 61 | ||
109 | @mixin maximized-base { | 62 | > * { |
110 | $nav-preview-vertical-padding: 40px; | 63 | font-size: 0.9em !important; |
64 | } | ||
65 | } | ||
111 | 66 | ||
112 | flex-direction: row; | 67 | .tab-content { |
113 | z-index: #{z(header) - 1}; | 68 | min-height: 75px; |
114 | position: fixed; | 69 | max-height: 210px; |
115 | top: $header-height; | 70 | padding: $base-padding; |
116 | left: $menu-width; | ||
117 | max-height: none !important; | ||
118 | max-width: none !important; | ||
119 | width: calc(100% - #{$menu-width}); | ||
120 | height: calc(100vh - #{$header-height}) !important; | ||
121 | 71 | ||
122 | .nav-preview { | 72 | overflow-y: auto; |
123 | @include nav-preview-medium(); | 73 | word-wrap: break-word; |
124 | @include padding-right(0); | ||
125 | @include padding-left(0); | ||
126 | 74 | ||
127 | padding-top: math.div($nav-preview-vertical-padding, 2); | 75 | border: 1px solid $input-border-color; |
128 | padding-bottom: math.div($nav-preview-vertical-padding, 2); | ||
129 | position: absolute; | ||
130 | background-color: pvar(--mainBackgroundColor); | ||
131 | width: 100% !important; | ||
132 | border-top: 0; | 76 | border-top: 0; |
133 | border-left: 0; | ||
134 | border-right: 0; | ||
135 | 77 | ||
136 | :last-child { | 78 | border-bottom-left-radius: $input-border-radius; |
137 | @include margin-right(pvar(--horizontalMarginContent)); | 79 | border-bottom-right-radius: $input-border-radius; |
138 | } | ||
139 | } | 80 | } |
140 | 81 | ||
141 | ::ng-deep .tab-content { | 82 | &.maximized { |
142 | @include content-preview-base(); | 83 | z-index: #{z(header) - 1}; |
143 | background-color: pvar(--mainBackgroundColor); | 84 | position: fixed; |
144 | scrollbar-color: pvar(--actionButtonColor) pvar(--mainBackgroundColor); | 85 | top: $header-height; |
145 | } | 86 | left: $menu-width; |
146 | 87 | ||
147 | textarea, | ||
148 | ::ng-deep .tab-content { | ||
149 | max-height: none !important; | 88 | max-height: none !important; |
150 | max-width: none !important; | 89 | max-width: none !important; |
151 | margin-top: #{$nav-preview-tab-height + $nav-preview-vertical-padding} !important; | 90 | width: calc(100% - #{$menu-width}); |
152 | height: calc(100vh - #{$header-height + $nav-preview-tab-height + $nav-preview-vertical-padding}) !important; | 91 | height: calc(100vh - #{$header-height}); |
153 | width: 50% !important; | ||
154 | border: 0 !important; | ||
155 | border-radius: unset !important; | ||
156 | } | ||
157 | |||
158 | :host-context(.expanded) { | ||
159 | .root.maximized { | ||
160 | left: 0; | ||
161 | width: 100%; | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | 92 | ||
166 | @mixin maximized-in-small-view { | 93 | display: grid; |
167 | .root.maximized { | 94 | grid-template-rows: auto 1fr; |
168 | @include maximized-base(); | 95 | grid-template-columns: 1fr 1fr; |
169 | 96 | ||
170 | textarea { | 97 | background-color: pvar(--mainBackgroundColor); |
171 | display: none; | ||
172 | } | ||
173 | |||
174 | ::ng-deep .tab-content { | ||
175 | width: 100% !important; | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | 98 | ||
180 | @mixin maximized-tabs-in-mobile-view { | ||
181 | // Ellipsis on tabs for mobile view | ||
182 | .root.maximized { | ||
183 | .nav-preview { | 99 | .nav-preview { |
184 | ::ng-deep .nav-link { | 100 | grid-row: 1; |
185 | @include ellipsis(); | 101 | grid-column: 1 / 3; |
186 | @include margin-right(10px !important); | ||
187 | 102 | ||
188 | display: block !important; | 103 | border: 0; |
189 | max-width: 45% !important; | 104 | border-bottom: 2px solid pvar(--mainColor); |
190 | padding: 5px 0 !important; | ||
191 | text-align: center; | ||
192 | 105 | ||
193 | &:not(.active) { | 106 | padding: 20px 0; |
194 | max-width: 15% !important; | 107 | width: 100% !important; |
195 | } | ||
196 | 108 | ||
197 | &.active { | 109 | .maximize-button { |
198 | padding: 5px 15px !important; | 110 | @include margin-right(15px); |
199 | } | ||
200 | } | 111 | } |
201 | } | 112 | } |
202 | } | ||
203 | } | ||
204 | 113 | ||
205 | @mixin in-medium-view { | 114 | textarea { |
206 | .root { | 115 | grid-column: 1; |
207 | .nav-preview { | 116 | |
208 | @include nav-preview-medium(); | 117 | border: 0; |
118 | border-right: 1px dashed $input-border-color; | ||
119 | |||
120 | resize: none; | ||
209 | } | 121 | } |
210 | 122 | ||
211 | ::ng-deep .tab-content { | 123 | ::ng-deep .tab-content { |
212 | @include content-preview-base(); | 124 | grid-column: 2; |
213 | max-height: 210px; | ||
214 | border-bottom: 1px solid $input-border-color; | ||
215 | border-left: 1px solid $input-border-color; | ||
216 | border-right: 1px solid $input-border-color; | ||
217 | border-bottom-left-radius: $input-border-radius; | ||
218 | border-bottom-right-radius: $input-border-radius; | ||
219 | } | ||
220 | } | ||
221 | } | ||
222 | 125 | ||
223 | @mixin maximized-in-medium-view { | 126 | border: 0; |
224 | .root.maximized { | ||
225 | @include maximized-base(); | ||
226 | 127 | ||
227 | textarea { | ||
228 | display: block; | 128 | display: block; |
229 | padding: $base-padding; | 129 | overflow-y: auto; |
230 | border-right: 1px dashed $input-border-color !important; | 130 | word-wrap: break-word; |
231 | resize: none; | ||
232 | scrollbar-color: pvar(--actionButtonColor) pvar(--markdownTextareaBackgroundColor); | ||
233 | 131 | ||
234 | &:focus { | 132 | scrollbar-color: pvar(--actionButtonColor) pvar(--mainBackgroundColor); |
235 | box-shadow: none; | ||
236 | } | ||
237 | } | 133 | } |
238 | } | ||
239 | } | ||
240 | 134 | ||
241 | @include in-small-view(); | 135 | textarea, |
242 | @include maximized-in-small-view(); | 136 | ::ng-deep .tab-content { |
137 | grid-row: 2; | ||
243 | 138 | ||
244 | @media only screen and (max-width: $mobile-view) { | 139 | height: 100% !important; |
245 | @include maximized-tabs-in-mobile-view(); | 140 | max-height: none !important; |
246 | } | 141 | border-radius: 0; |
247 | 142 | ||
248 | @media only screen and (max-width: #{$mobile-view + $menu-width}) { | 143 | padding: 15px; |
249 | :host-context(.main-col:not(.expanded)) { | 144 | } |
250 | @include maximized-tabs-in-mobile-view(); | 145 | |
251 | } | 146 | @media screen and (max-width: $mobile-view) { |
252 | } | 147 | grid-template-rows: auto 45% 1fr; |
148 | grid-template-columns: 1fr; | ||
149 | |||
150 | .nav-preview { | ||
151 | grid-column: 1; | ||
152 | } | ||
253 | 153 | ||
254 | @media only screen and (min-width: $small-view) { | 154 | textarea { |
255 | @include maximized-in-medium-view(); | 155 | grid-row: 2; |
156 | grid-column: 1; | ||
157 | border: 0; | ||
158 | border-bottom: 1px dashed $input-border-color;; | ||
159 | } | ||
256 | 160 | ||
257 | :host-context(.expanded) { | 161 | ::ng-deep .tab-content { |
258 | @include in-medium-view(); | 162 | grid-column: 1; |
163 | grid-row: 3; | ||
164 | } | ||
165 | } | ||
259 | } | 166 | } |
260 | } | 167 | } |
261 | 168 | ||
262 | @media only screen and (min-width: #{$small-view + $menu-width}) { | 169 | :host-context(.main-col.expanded) { |
263 | :host-context(.main-col:not(.expanded)) { | 170 | .root.maximized { |
264 | @include in-medium-view(); | 171 | left: 0; |
172 | width: 100%; | ||
265 | } | 173 | } |
266 | } | 174 | } |
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.ts b/client/src/app/shared/shared-forms/markdown-textarea.component.ts index dcb5d20da..089991884 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.ts +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.ts | |||
@@ -24,10 +24,7 @@ import { Video } from '@shared/models' | |||
24 | export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { | 24 | export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { |
25 | @Input() content = '' | 25 | @Input() content = '' |
26 | 26 | ||
27 | @Input() classes: string[] | { [klass: string]: any[] | any } = [] | 27 | @Input() formError: string |
28 | |||
29 | @Input() textareaMaxWidth = '100%' | ||
30 | @Input() textareaHeight = '150px' | ||
31 | 28 | ||
32 | @Input() truncate: number | 29 | @Input() truncate: number |
33 | 30 | ||
@@ -93,6 +90,8 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { | |||
93 | } | 90 | } |
94 | 91 | ||
95 | onMaximizeClick () { | 92 | onMaximizeClick () { |
93 | if (this.disabled) return | ||
94 | |||
96 | this.isMaximized = !this.isMaximized | 95 | this.isMaximized = !this.isMaximized |
97 | 96 | ||
98 | // Make sure textarea have the focus | 97 | // Make sure textarea have the focus |
diff --git a/client/src/app/shared/shared-forms/peertube-checkbox.component.html b/client/src/app/shared/shared-forms/peertube-checkbox.component.html index c679e1403..cd06e75ba 100644 --- a/client/src/app/shared/shared-forms/peertube-checkbox.component.html +++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="root flex-column"> | 1 | <div class="root flex-column"> |
2 | <div class="d-flex"> | 2 | <div class="d-flex"> |
3 | <label class="form-group-checkbox"> | 3 | <label> |
4 | <input | 4 | <input |
5 | type="checkbox" | 5 | type="checkbox" |
6 | [(ngModel)]="checked" | 6 | [(ngModel)]="checked" |
@@ -33,10 +33,10 @@ | |||
33 | <div *ngIf="recommended" class="recommended" i18n>Recommended</div> | 33 | <div *ngIf="recommended" class="recommended" i18n>Recommended</div> |
34 | </div> | 34 | </div> |
35 | 35 | ||
36 | <div class="ml-4 d-flex flex-column"> | 36 | <div class="ms-4 d-flex flex-column"> |
37 | <small class="wrapper mt-2 muted"> | 37 | <div class="wrapper form-group-description"> |
38 | <ng-content select="description"></ng-content> | 38 | <ng-content select="description"></ng-content> |
39 | </small> | 39 | </div> |
40 | 40 | ||
41 | <span class="wrapper mt-3"> | 41 | <span class="wrapper mt-3"> |
42 | <ng-content select="extra"></ng-content> | 42 | <ng-content select="extra"></ng-content> |
diff --git a/client/src/app/shared/shared-forms/peertube-checkbox.component.scss b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss index 4e384e7e0..ac9127677 100644 --- a/client/src/app/shared/shared-forms/peertube-checkbox.component.scss +++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss | |||
@@ -4,14 +4,9 @@ | |||
4 | .root { | 4 | .root { |
5 | display: flex; | 5 | display: flex; |
6 | 6 | ||
7 | .form-group-checkbox { | 7 | label { |
8 | display: flex; | 8 | display: flex; |
9 | align-items: center; | 9 | font-size: $form-input-font-size; |
10 | |||
11 | .label-text { | ||
12 | font-weight: $font-regular; | ||
13 | margin: 0; | ||
14 | } | ||
15 | 10 | ||
16 | input { | 11 | input { |
17 | @include peertube-checkbox(1px); | 12 | @include peertube-checkbox(1px); |
@@ -23,12 +18,11 @@ | |||
23 | } | 18 | } |
24 | 19 | ||
25 | my-help { | 20 | my-help { |
26 | position: relative; | 21 | line-height: 17px; |
27 | top: 2px; | ||
28 | } | 22 | } |
29 | 23 | ||
30 | small { | 24 | .form-group-description { |
31 | font-size: 90%; | 25 | margin-top: 2px; |
32 | } | 26 | } |
33 | 27 | ||
34 | .wrapper:empty { | 28 | .wrapper:empty { |
diff --git a/client/src/app/shared/shared-forms/reactive-file.component.ts b/client/src/app/shared/shared-forms/reactive-file.component.ts index 50b7d4c3e..48055a51c 100644 --- a/client/src/app/shared/shared-forms/reactive-file.component.ts +++ b/client/src/app/shared/shared-forms/reactive-file.component.ts | |||
@@ -57,7 +57,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor { | |||
57 | 57 | ||
58 | const extension = '.' + file.name.split('.').pop() | 58 | const extension = '.' + file.name.split('.').pop() |
59 | if (this.extensions.includes(extension.toLowerCase()) === false) { | 59 | if (this.extensions.includes(extension.toLowerCase()) === false) { |
60 | const message = $localize`PeerTube cannot handle this kind of file. Accepted extensions are ${this.allowedExtensionsMessage}}.` | 60 | const message = $localize`PeerTube cannot handle this kind of file. Accepted extensions are ${this.allowedExtensionsMessage}.` |
61 | this.notifier.error(message) | 61 | this.notifier.error(message) |
62 | 62 | ||
63 | return | 63 | return |
diff --git a/client/src/app/shared/shared-forms/select/select-channel.component.html b/client/src/app/shared/shared-forms/select/select-channel.component.html index f83f17a16..b49fd36fa 100644 --- a/client/src/app/shared/shared-forms/select/select-channel.component.html +++ b/client/src/app/shared/shared-forms/select/select-channel.component.html | |||
@@ -7,7 +7,7 @@ | |||
7 | [searchable]="searchable" | 7 | [searchable]="searchable" |
8 | > | 8 | > |
9 | <ng-option *ngFor="let channel of channels" [value]="channel.id"> | 9 | <ng-option *ngFor="let channel of channels" [value]="channel.id"> |
10 | <img alt="" class="avatar mr-1" [src]="channel.avatarPath" /> | 10 | <img alt="" class="avatar me-1" [src]="channel.avatarPath" /> |
11 | {{ channel.label }} | 11 | {{ channel.label }} |
12 | </ng-option> | 12 | </ng-option> |
13 | </ng-select> | 13 | </ng-select> |
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts b/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts index ebf7b77a6..2c3226f68 100644 --- a/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts +++ b/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, forwardRef, Input } from '@angular/core' | 1 | import { Component, forwardRef, Input } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { prepareIcu } from '@app/helpers' | ||
4 | import { SelectOptionsItem } from '../../../../types/select-options-item.model' | 5 | import { SelectOptionsItem } from '../../../../types/select-options-item.model' |
5 | import { ItemSelectCheckboxValue } from './select-checkbox.component' | 6 | import { ItemSelectCheckboxValue } from './select-checkbox.component' |
6 | 7 | ||
@@ -78,7 +79,12 @@ export class SelectCheckboxAllComponent implements ControlValueAccessor { | |||
78 | if (!outputItems) return true | 79 | if (!outputItems) return true |
79 | 80 | ||
80 | if (outputItems.length >= this.maxItems) { | 81 | if (outputItems.length >= this.maxItems) { |
81 | this.notifier.error($localize`You can't select more than ${this.maxItems} items`) | 82 | this.notifier.error( |
83 | prepareIcu($localize`You can't select more than {maxItems, plural, =1 {1 item} other {{maxItems} items}}`)( | ||
84 | { maxItems: this.maxItems }, | ||
85 | $localize`You can't select more than ${this.maxItems} items` | ||
86 | ) | ||
87 | ) | ||
82 | 88 | ||
83 | return false | 89 | return false |
84 | } | 90 | } |
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox.component.html b/client/src/app/shared/shared-forms/select/select-checkbox.component.html index 03db2875b..2799ccdcc 100644 --- a/client/src/app/shared/shared-forms/select/select-checkbox.component.html +++ b/client/src/app/shared/shared-forms/select/select-checkbox.component.html | |||
@@ -22,7 +22,7 @@ | |||
22 | > | 22 | > |
23 | 23 | ||
24 | <ng-template ng-optgroup-tmp let-item="item" let-item$="item$" let-index="index"> | 24 | <ng-template ng-optgroup-tmp let-item="item" let-item$="item$" let-index="index"> |
25 | <div class="form-group-checkbox"> | 25 | <div class="checkbox-wrapper"> |
26 | <input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/> | 26 | <input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/> |
27 | <span role="checkbox" [attr.aria-checked]="item$.selected"></span> | 27 | <span role="checkbox" [attr.aria-checked]="item$.selected"></span> |
28 | <span>{{ item.group }}</span> | 28 | <span>{{ item.group }}</span> |
@@ -30,7 +30,7 @@ | |||
30 | </ng-template> | 30 | </ng-template> |
31 | 31 | ||
32 | <ng-template ng-option-tmp let-item="item" let-item$="item$" let-index="index"> | 32 | <ng-template ng-option-tmp let-item="item" let-item$="item$" let-index="index"> |
33 | <div class="form-group-checkbox"> | 33 | <div class="checkbox-wrapper"> |
34 | <input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/> | 34 | <input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/> |
35 | <span role="checkbox" [attr.aria-checked]="item$.selected"></span> | 35 | <span role="checkbox" [attr.aria-checked]="item$.selected"></span> |
36 | <span>{{ item.label }}</span> | 36 | <span>{{ item.label }}</span> |
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox.component.scss b/client/src/app/shared/shared-forms/select/select-checkbox.component.scss index d47c4f9da..892f22dff 100644 --- a/client/src/app/shared/shared-forms/select/select-checkbox.component.scss +++ b/client/src/app/shared/shared-forms/select/select-checkbox.component.scss | |||
@@ -7,7 +7,7 @@ ng-select ::ng-deep { | |||
7 | align-items: center; | 7 | align-items: center; |
8 | } | 8 | } |
9 | 9 | ||
10 | .form-group-checkbox { | 10 | .checkbox-wrapper { |
11 | display: flex; | 11 | display: flex; |
12 | align-items: center; | 12 | align-items: center; |
13 | 13 | ||
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts index 60c2f66ae..81f076db6 100644 --- a/client/src/app/shared/shared-forms/shared-form.module.ts +++ b/client/src/app/shared/shared-forms/shared-form.module.ts | |||
@@ -9,7 +9,7 @@ import { AdvancedInputFilterComponent } from './advanced-input-filter.component' | |||
9 | import { DynamicFormFieldComponent } from './dynamic-form-field.component' | 9 | import { DynamicFormFieldComponent } from './dynamic-form-field.component' |
10 | import { FormValidatorService } from './form-validator.service' | 10 | import { FormValidatorService } from './form-validator.service' |
11 | import { InputSwitchComponent } from './input-switch.component' | 11 | import { InputSwitchComponent } from './input-switch.component' |
12 | import { InputToggleHiddenComponent } from './input-toggle-hidden.component' | 12 | import { InputTextComponent } from './input-text.component' |
13 | import { MarkdownTextareaComponent } from './markdown-textarea.component' | 13 | import { MarkdownTextareaComponent } from './markdown-textarea.component' |
14 | import { PeertubeCheckboxComponent } from './peertube-checkbox.component' | 14 | import { PeertubeCheckboxComponent } from './peertube-checkbox.component' |
15 | import { PreviewUploadComponent } from './preview-upload.component' | 15 | import { PreviewUploadComponent } from './preview-upload.component' |
@@ -40,7 +40,7 @@ import { TimestampInputComponent } from './timestamp-input.component' | |||
40 | ], | 40 | ], |
41 | 41 | ||
42 | declarations: [ | 42 | declarations: [ |
43 | InputToggleHiddenComponent, | 43 | InputTextComponent, |
44 | MarkdownTextareaComponent, | 44 | MarkdownTextareaComponent, |
45 | PeertubeCheckboxComponent, | 45 | PeertubeCheckboxComponent, |
46 | PreviewUploadComponent, | 46 | PreviewUploadComponent, |
@@ -71,7 +71,7 @@ import { TimestampInputComponent } from './timestamp-input.component' | |||
71 | InputMaskModule, | 71 | InputMaskModule, |
72 | NgSelectModule, | 72 | NgSelectModule, |
73 | 73 | ||
74 | InputToggleHiddenComponent, | 74 | InputTextComponent, |
75 | MarkdownTextareaComponent, | 75 | MarkdownTextareaComponent, |
76 | PeertubeCheckboxComponent, | 76 | PeertubeCheckboxComponent, |
77 | PreviewUploadComponent, | 77 | PreviewUploadComponent, |
diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.scss b/client/src/app/shared/shared-forms/timestamp-input.component.scss index 27d6fa173..e69a06947 100644 --- a/client/src/app/shared/shared-forms/timestamp-input.component.scss +++ b/client/src/app/shared/shared-forms/timestamp-input.component.scss | |||
@@ -4,7 +4,6 @@ | |||
4 | p-inputmask { | 4 | p-inputmask { |
5 | ::ng-deep input { | 5 | ::ng-deep input { |
6 | width: 80px; | 6 | width: 80px; |
7 | font-size: 15px; | ||
8 | 7 | ||
9 | &:focus-within, | 8 | &:focus-within, |
10 | &:focus { | 9 | &:focus { |
diff --git a/client/src/app/shared/shared-icons/global-icon.component.scss b/client/src/app/shared/shared-icons/global-icon.component.scss index 6795d6628..10180829f 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.scss +++ b/client/src/app/shared/shared-icons/global-icon.component.scss | |||
@@ -3,4 +3,9 @@ | |||
3 | width: inherit; | 3 | width: inherit; |
4 | height: inherit; | 4 | height: inherit; |
5 | } | 5 | } |
6 | |||
7 | .feather-flag { | ||
8 | margin-left: -1px; | ||
9 | } | ||
6 | } | 10 | } |
11 | |||
diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts index ba23edde0..55eb45a75 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts | |||
@@ -21,6 +21,7 @@ const icons = { | |||
21 | local: require('!!raw-loader?!../../../assets/images/misc/local.svg').default, | 21 | local: require('!!raw-loader?!../../../assets/images/misc/local.svg').default, |
22 | 22 | ||
23 | // feather icons | 23 | // feather icons |
24 | copy: require('!!raw-loader?!../../../assets/images/feather/copy.svg').default, | ||
24 | flag: require('!!raw-loader?!../../../assets/images/feather/flag.svg').default, | 25 | flag: require('!!raw-loader?!../../../assets/images/feather/flag.svg').default, |
25 | playlists: require('!!raw-loader?!../../../assets/images/feather/list.svg').default, | 26 | playlists: require('!!raw-loader?!../../../assets/images/feather/list.svg').default, |
26 | syndication: require('!!raw-loader?!../../../assets/images/feather/syndication.svg').default, | 27 | syndication: require('!!raw-loader?!../../../assets/images/feather/syndication.svg').default, |
@@ -28,7 +29,6 @@ const icons = { | |||
28 | alert: require('!!raw-loader?!../../../assets/images/feather/alert.svg').default, | 29 | alert: require('!!raw-loader?!../../../assets/images/feather/alert.svg').default, |
29 | globe: require('!!raw-loader?!../../../assets/images/feather/globe.svg').default, | 30 | globe: require('!!raw-loader?!../../../assets/images/feather/globe.svg').default, |
30 | home: require('!!raw-loader?!../../../assets/images/feather/home.svg').default, | 31 | home: require('!!raw-loader?!../../../assets/images/feather/home.svg').default, |
31 | 'recently-added': require('!!raw-loader?!../../../assets/images/feather/recently-added.svg').default, | ||
32 | trending: require('!!raw-loader?!../../../assets/images/feather/trending.svg').default, | 32 | trending: require('!!raw-loader?!../../../assets/images/feather/trending.svg').default, |
33 | search: require('!!raw-loader?!../../../assets/images/feather/search.svg').default, | 33 | search: require('!!raw-loader?!../../../assets/images/feather/search.svg').default, |
34 | upload: require('!!raw-loader?!../../../assets/images/feather/upload.svg').default, | 34 | upload: require('!!raw-loader?!../../../assets/images/feather/upload.svg').default, |
@@ -62,8 +62,9 @@ const icons = { | |||
62 | 'exit-fullscreen': require('!!raw-loader?!../../../assets/images/feather/minimize.svg').default, | 62 | 'exit-fullscreen': require('!!raw-loader?!../../../assets/images/feather/minimize.svg').default, |
63 | film: require('!!raw-loader?!../../../assets/images/feather/film.svg').default, | 63 | film: require('!!raw-loader?!../../../assets/images/feather/film.svg').default, |
64 | edit: require('!!raw-loader?!../../../assets/images/feather/edit-2.svg').default, | 64 | edit: require('!!raw-loader?!../../../assets/images/feather/edit-2.svg').default, |
65 | sensitive: require('!!raw-loader?!../../../assets/images/feather/eye.svg').default, | 65 | 'external-link': require('!!raw-loader?!../../../assets/images/feather/external-link.svg').default, |
66 | unsensitive: require('!!raw-loader?!../../../assets/images/feather/eye-off.svg').default, | 66 | 'eye-open': require('!!raw-loader?!../../../assets/images/feather/eye.svg').default, |
67 | 'eye-close': require('!!raw-loader?!../../../assets/images/feather/eye-off.svg').default, | ||
67 | refresh: require('!!raw-loader?!../../../assets/images/feather/refresh-cw.svg').default, | 68 | refresh: require('!!raw-loader?!../../../assets/images/feather/refresh-cw.svg').default, |
68 | command: require('!!raw-loader?!../../../assets/images/feather/command.svg').default, | 69 | command: require('!!raw-loader?!../../../assets/images/feather/command.svg').default, |
69 | go: require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default, | 70 | go: require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default, |
@@ -89,6 +90,7 @@ export type GlobalIconName = keyof typeof icons | |||
89 | }) | 90 | }) |
90 | export class GlobalIconComponent implements OnInit { | 91 | export class GlobalIconComponent implements OnInit { |
91 | @Input() iconName: GlobalIconName | 92 | @Input() iconName: GlobalIconName |
93 | @Input() width: string | ||
92 | 94 | ||
93 | constructor ( | 95 | constructor ( |
94 | private el: ElementRef, | 96 | private el: ElementRef, |
@@ -104,6 +106,10 @@ export class GlobalIconComponent implements OnInit { | |||
104 | 'filter:internal.common.svg-icons.get-content.params', | 106 | 'filter:internal.common.svg-icons.get-content.params', |
105 | 'filter:internal.common.svg-icons.get-content.result' | 107 | 'filter:internal.common.svg-icons.get-content.result' |
106 | ) | 108 | ) |
109 | |||
110 | if (this.width) { | ||
111 | nativeElement.style.width = this.width | ||
112 | } | ||
107 | } | 113 | } |
108 | 114 | ||
109 | private getSVGContent (options: { name: string }) { | 115 | private getSVGContent (options: { name: string }) { |
diff --git a/client/src/app/shared/shared-instance/feature-boolean.component.html b/client/src/app/shared/shared-instance/feature-boolean.component.html index ccb8a30cc..6de5d2075 100644 --- a/client/src/app/shared/shared-instance/feature-boolean.component.html +++ b/client/src/app/shared/shared-instance/feature-boolean.component.html | |||
@@ -1,3 +1,2 @@ | |||
1 | <span *ngIf="value === true" class="glyphicon glyphicon-ok" i18n-aria-label aria-label="yes"></span> | 1 | <my-global-icon *ngIf="value === true" iconName="tick" i18n-aria-label aria-label="yes"></my-global-icon> |
2 | <span *ngIf="value === false" class="glyphicon glyphicon-remove" i18n-aria-label aria-label="no"></span> | 2 | <my-global-icon *ngIf="value === false" iconName="cross" i18n-aria-label aria-label="no"></my-global-icon> |
3 | |||
diff --git a/client/src/app/shared/shared-instance/feature-boolean.component.scss b/client/src/app/shared/shared-instance/feature-boolean.component.scss index 8683ecc55..29b8c3e02 100644 --- a/client/src/app/shared/shared-instance/feature-boolean.component.scss +++ b/client/src/app/shared/shared-instance/feature-boolean.component.scss | |||
@@ -1,10 +1,10 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .glyphicon-ok { | 4 | my-global-icon[iconName=tick] { |
5 | color: $green; | 5 | color: $green; |
6 | } | 6 | } |
7 | 7 | ||
8 | .glyphicon-remove { | 8 | my-global-icon[iconName=cross] { |
9 | color: $red; | 9 | color: $red; |
10 | } | 10 | } |
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.html b/client/src/app/shared/shared-instance/instance-about-accordion.component.html index 73e511d1c..466d73ca4 100644 --- a/client/src/app/shared/shared-instance/instance-about-accordion.component.html +++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <h2 class="instance-name">{{ about?.instance.name }}</h2> | 1 | <h2 *ngIf="displayInstanceName" class="instance-name">{{ about?.instance.name }}</h2> |
2 | 2 | ||
3 | <div class="instance-short-description">{{ about?.instance.shortDescription }}</div> | 3 | <div *ngIf="displayInstanceShortDescription" class="instance-short-description">{{ about?.instance.shortDescription }}</div> |
4 | 4 | ||
5 | <ngb-accordion #accordion="ngbAccordion" [closeOthers]="true"> | 5 | <ngb-accordion #accordion="ngbAccordion" [closeOthers]="true"> |
6 | <ngb-panel *ngIf="panels.features" id="instance-features" i18n-title title="Features found on this instance"> | 6 | <ngb-panel *ngIf="panels.features" id="instance-features" i18n-title title="Features found on this instance"> |
@@ -32,7 +32,7 @@ | |||
32 | </ng-template> | 32 | </ng-template> |
33 | </ngb-panel> | 33 | </ngb-panel> |
34 | 34 | ||
35 | <ngb-panel *ngIf="termsPanel" id="terms" i18n-title title="Terms"> | 35 | <ngb-panel *ngIf="termsPanel" id="terms" [title]="getTermsTitle()"> |
36 | <ng-template ngbPanelContent> | 36 | <ng-template ngbPanelContent> |
37 | <div class="block" [innerHTML]="aboutHtml.terms"></div> | 37 | <div class="block" [innerHTML]="aboutHtml.terms"></div> |
38 | </ng-template> | 38 | </ng-template> |
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss index be6099a97..c8e288462 100644 --- a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss +++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss | |||
@@ -8,34 +8,9 @@ | |||
8 | .instance-short-description { | 8 | .instance-short-description { |
9 | @include ellipsis-multiline(1rem, 3); | 9 | @include ellipsis-multiline(1rem, 3); |
10 | 10 | ||
11 | margin-top: 20px; | 11 | margin: 25px 0; |
12 | margin-bottom: 20px; | ||
13 | } | 12 | } |
14 | 13 | ||
15 | .block { | 14 | .block { |
16 | font-size: 15px; | ||
17 | margin-bottom: 15px; | 15 | margin-bottom: 15px; |
18 | } | 16 | } |
19 | |||
20 | ngb-accordion ::ng-deep { | ||
21 | .card { | ||
22 | border-color: var(--mainBackgroundColor); | ||
23 | |||
24 | .card-header { | ||
25 | background-color: unset; | ||
26 | padding: 0; | ||
27 | |||
28 | + .collapse.show { | ||
29 | background-color: var(--submenuBackgroundColor); | ||
30 | } | ||
31 | } | ||
32 | } | ||
33 | |||
34 | .btn { | ||
35 | @include peertube-button; | ||
36 | @include grey-button; | ||
37 | |||
38 | border-radius: unset; | ||
39 | width: 100%; | ||
40 | } | ||
41 | } | ||
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts index b9f57e2a4..e13703c03 100644 --- a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts +++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts | |||
@@ -15,6 +15,9 @@ export class InstanceAboutAccordionComponent implements OnInit { | |||
15 | 15 | ||
16 | @Output() init: EventEmitter<InstanceAboutAccordionComponent> = new EventEmitter<InstanceAboutAccordionComponent>() | 16 | @Output() init: EventEmitter<InstanceAboutAccordionComponent> = new EventEmitter<InstanceAboutAccordionComponent>() |
17 | 17 | ||
18 | @Input() displayInstanceName = true | ||
19 | @Input() displayInstanceShortDescription = true | ||
20 | |||
18 | @Input() pluginScope: PluginClientScope | 21 | @Input() pluginScope: PluginClientScope |
19 | @Input() pluginHook: ClientFilterHookName | 22 | @Input() pluginHook: ClientFilterHookName |
20 | 23 | ||
@@ -66,6 +69,10 @@ export class InstanceAboutAccordionComponent implements OnInit { | |||
66 | return !!(this.aboutHtml?.administrator || this.about?.instance.maintenanceLifetime || this.about?.instance.businessModel) | 69 | return !!(this.aboutHtml?.administrator || this.about?.instance.maintenanceLifetime || this.about?.instance.businessModel) |
67 | } | 70 | } |
68 | 71 | ||
72 | getTermsTitle () { | ||
73 | return $localize`Terms of ${this.about.instance.name}` | ||
74 | } | ||
75 | |||
69 | get moderationPanel () { | 76 | get moderationPanel () { |
70 | return this.panels.moderation && !!this.aboutHtml.moderationInformation | 77 | return this.panels.moderation && !!this.aboutHtml.moderationInformation |
71 | } | 78 | } |
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.html b/client/src/app/shared/shared-instance/instance-features-table.component.html index 1fdef95ff..761243bfe 100644 --- a/client/src/app/shared/shared-instance/instance-features-table.component.html +++ b/client/src/app/shared/shared-instance/instance-features-table.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div *ngIf="serverConfig" class="feature-table"> | 1 | <div *ngIf="serverConfig" class="feature-table"> |
2 | 2 | ||
3 | <table class="table" *ngIf="serverConfig"> | 3 | <table *ngIf="serverConfig"> |
4 | <caption i18n>Features found on this instance</caption> | 4 | <caption i18n>Features found on this instance</caption> |
5 | <tr> | 5 | <tr> |
6 | <th i18n class="label" scope="row">PeerTube version</th> | 6 | <th i18n class="label" scope="row">PeerTube version</th> |
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.scss b/client/src/app/shared/shared-instance/instance-features-table.component.scss index 56ca105f4..bfae0c112 100644 --- a/client/src/app/shared/shared-instance/instance-features-table.component.scss +++ b/client/src/app/shared/shared-instance/instance-features-table.component.scss | |||
@@ -4,6 +4,7 @@ | |||
4 | table { | 4 | table { |
5 | font-size: 14px; | 5 | font-size: 14px; |
6 | color: pvar(--mainForegroundColor); | 6 | color: pvar(--mainForegroundColor); |
7 | width: 100%; | ||
7 | 8 | ||
8 | .label, | 9 | .label, |
9 | .sub-label { | 10 | .sub-label { |
@@ -24,13 +25,14 @@ table { | |||
24 | } | 25 | } |
25 | } | 26 | } |
26 | 27 | ||
28 | th, | ||
27 | td { | 29 | td { |
28 | vertical-align: middle; | 30 | padding: 0.75rem; |
31 | border-top: 1px solid #dee2e6; | ||
29 | } | 32 | } |
30 | 33 | ||
31 | caption { | 34 | caption { |
32 | caption-side: top; | 35 | caption-side: top; |
33 | font-size: 15px; | ||
34 | font-weight: $font-semibold; | 36 | font-weight: $font-semibold; |
35 | color: pvar(--mainForegroundColor); | 37 | color: pvar(--mainForegroundColor); |
36 | } | 38 | } |
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.ts b/client/src/app/shared/shared-instance/instance-features-table.component.ts index 6335de450..e405c5790 100644 --- a/client/src/app/shared/shared-instance/instance-features-table.component.ts +++ b/client/src/app/shared/shared-instance/instance-features-table.component.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { prepareIcu } from '@app/helpers' | ||
3 | import { ServerConfig } from '@shared/models' | 4 | import { ServerConfig } from '@shared/models' |
4 | import { PeertubeModalService } from '../shared-main/peertube-modal/peertube-modal.service' | 5 | import { PeertubeModalService } from '../shared-main/peertube-modal/peertube-modal.service' |
5 | 6 | ||
@@ -65,15 +66,20 @@ export class InstanceFeaturesTableComponent implements OnInit { | |||
65 | 66 | ||
66 | private getApproximateTime (seconds: number) { | 67 | private getApproximateTime (seconds: number) { |
67 | const hours = Math.floor(seconds / 3600) | 68 | const hours = Math.floor(seconds / 3600) |
68 | let pluralSuffix = '' | ||
69 | if (hours > 1) pluralSuffix = 's' | ||
70 | if (hours > 0) return `~ ${hours} hour${pluralSuffix}` | ||
71 | 69 | ||
72 | const minutes = Math.floor(seconds % 3600 / 60) | 70 | if (hours !== 0) { |
71 | return prepareIcu($localize`~ {hours, plural, =1 {1 hour} other {{hours} hours}}`)( | ||
72 | { hours }, | ||
73 | $localize`~ ${hours} hours` | ||
74 | ) | ||
75 | } | ||
73 | 76 | ||
74 | if (minutes === 1) return $localize`~ 1 minute` | 77 | const minutes = Math.floor(seconds % 3600 / 60) |
75 | 78 | ||
76 | return $localize`~ ${minutes} minutes` | 79 | return prepareIcu($localize`~ {minutes, plural, =1 {1 minute} other {{minutes} minutes}}`)( |
80 | { minutes }, | ||
81 | $localize`~ ${minutes} minutes` | ||
82 | ) | ||
77 | } | 83 | } |
78 | 84 | ||
79 | private buildQuotaHelpIndication () { | 85 | private buildQuotaHelpIndication () { |
diff --git a/client/src/app/shared/shared-instance/instance-statistics.component.html b/client/src/app/shared/shared-instance/instance-statistics.component.html index 2ca61fd94..68b209990 100644 --- a/client/src/app/shared/shared-instance/instance-statistics.component.html +++ b/client/src/app/shared/shared-instance/instance-statistics.component.html | |||
@@ -10,7 +10,7 @@ | |||
10 | <p class="stat-value">{{ serverStats.totalUsers | number }}</p> | 10 | <p class="stat-value">{{ serverStats.totalUsers | number }}</p> |
11 | <p class="stat-label" i18n>users</p> | 11 | <p class="stat-label" i18n>users</p> |
12 | </div> | 12 | </div> |
13 | <i class="glyphicon glyphicon-user icon-bottom"></i> | 13 | <my-global-icon iconName="user"></my-global-icon> |
14 | </div> | 14 | </div> |
15 | </div> | 15 | </div> |
16 | 16 | ||
@@ -20,7 +20,7 @@ | |||
20 | <p class="stat-value">{{ serverStats.totalLocalVideos | number }}</p> | 20 | <p class="stat-value">{{ serverStats.totalLocalVideos | number }}</p> |
21 | <p class="stat-label" i18n>videos</p> | 21 | <p class="stat-label" i18n>videos</p> |
22 | </div> | 22 | </div> |
23 | <i class="glyphicon glyphicon-facetime-video"></i> | 23 | <my-global-icon iconName="film"></my-global-icon> |
24 | </div> | 24 | </div> |
25 | </div> | 25 | </div> |
26 | 26 | ||
@@ -30,7 +30,7 @@ | |||
30 | <p class="stat-value">{{ serverStats.totalLocalVideoViews | number }}</p> | 30 | <p class="stat-value">{{ serverStats.totalLocalVideoViews | number }}</p> |
31 | <p class="stat-label" i18n>views</p> | 31 | <p class="stat-label" i18n>views</p> |
32 | </div> | 32 | </div> |
33 | <i class="glyphicon glyphicon-eye-open"></i> | 33 | <my-global-icon iconName="eye-open"></my-global-icon> |
34 | </div> | 34 | </div> |
35 | </div> | 35 | </div> |
36 | 36 | ||
@@ -40,7 +40,7 @@ | |||
40 | <p class="stat-value">{{ serverStats.totalLocalVideoComments | number }}</p> | 40 | <p class="stat-value">{{ serverStats.totalLocalVideoComments | number }}</p> |
41 | <p class="stat-label" i18n>comments</p> | 41 | <p class="stat-label" i18n>comments</p> |
42 | </div> | 42 | </div> |
43 | <i class="glyphicon glyphicon-comment"></i> | 43 | <my-global-icon iconName="message-circle"></my-global-icon> |
44 | </div> | 44 | </div> |
45 | </div> | 45 | </div> |
46 | 46 | ||
@@ -50,7 +50,7 @@ | |||
50 | <p class="stat-value">{{ serverStats.totalLocalVideoFilesSize | bytes:1 }}</p> | 50 | <p class="stat-value">{{ serverStats.totalLocalVideoFilesSize | bytes:1 }}</p> |
51 | <p class="stat-label" i18n>hosted video</p> | 51 | <p class="stat-label" i18n>hosted video</p> |
52 | </div> | 52 | </div> |
53 | <i class="glyphicon glyphicon-hdd"></i> | 53 | <my-global-icon iconName="home"></my-global-icon> |
54 | </div> | 54 | </div> |
55 | </div> | 55 | </div> |
56 | </div> | 56 | </div> |
@@ -64,7 +64,7 @@ | |||
64 | <p class="stat-value">{{ serverStats.totalVideos | number }}</p> | 64 | <p class="stat-value">{{ serverStats.totalVideos | number }}</p> |
65 | <p class="stat-label" i18n>videos</p> | 65 | <p class="stat-label" i18n>videos</p> |
66 | </div> | 66 | </div> |
67 | <i class="glyphicon glyphicon-facetime-video"></i> | 67 | <my-global-icon iconName="film"></my-global-icon> |
68 | </div> | 68 | </div> |
69 | </div> | 69 | </div> |
70 | 70 | ||
@@ -74,7 +74,7 @@ | |||
74 | <p class="stat-value">{{ serverStats.totalVideoComments | number }}</p> | 74 | <p class="stat-value">{{ serverStats.totalVideoComments | number }}</p> |
75 | <p class="stat-label" i18n>comments</p> | 75 | <p class="stat-label" i18n>comments</p> |
76 | </div> | 76 | </div> |
77 | <i class="glyphicon glyphicon-comment"></i> | 77 | <my-global-icon iconName="message-circle"></my-global-icon> |
78 | </div> | 78 | </div> |
79 | </div> | 79 | </div> |
80 | 80 | ||
@@ -84,7 +84,7 @@ | |||
84 | <p class="stat-value">{{ serverStats.totalInstanceFollowers | number }}</p> | 84 | <p class="stat-value">{{ serverStats.totalInstanceFollowers | number }}</p> |
85 | <p class="stat-label" i18n>followers</p> | 85 | <p class="stat-label" i18n>followers</p> |
86 | </div> | 86 | </div> |
87 | <i class="glyphicon glyphicon-retweet"></i> | 87 | <my-global-icon iconName="share"></my-global-icon> |
88 | </div> | 88 | </div> |
89 | </div> | 89 | </div> |
90 | 90 | ||
@@ -94,7 +94,7 @@ | |||
94 | <p class="stat-value">{{ serverStats.totalInstanceFollowing | number }}</p> | 94 | <p class="stat-value">{{ serverStats.totalInstanceFollowing | number }}</p> |
95 | <p class="stat-label" i18n>following</p> | 95 | <p class="stat-label" i18n>following</p> |
96 | </div> | 96 | </div> |
97 | <i class="glyphicon glyphicon-retweet"></i> | 97 | <my-global-icon iconName="globe"></my-global-icon> |
98 | </div> | 98 | </div> |
99 | </div> | 99 | </div> |
100 | </div> | 100 | </div> |
diff --git a/client/src/app/shared/shared-instance/instance-statistics.component.scss b/client/src/app/shared/shared-instance/instance-statistics.component.scss index 5286ab03a..e1d489d28 100644 --- a/client/src/app/shared/shared-instance/instance-statistics.component.scss +++ b/client/src/app/shared/shared-instance/instance-statistics.component.scss | |||
@@ -1,3 +1,5 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
1 | 3 | ||
2 | h3 { | 4 | h3 { |
3 | font-size: 1.25rem; | 5 | font-size: 1.25rem; |
@@ -19,22 +21,19 @@ h3 { | |||
19 | margin: 0; | 21 | margin: 0; |
20 | } | 22 | } |
21 | 23 | ||
22 | .glyphicon { | ||
23 | opacity: 0.12; | ||
24 | position: absolute; | ||
25 | left: 16px; | ||
26 | top: -24px; | ||
27 | |||
28 | &.icon-bottom { | ||
29 | top: 4px; | ||
30 | } | ||
31 | |||
32 | &::before { | ||
33 | font-size: 8em; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | .card-body { | 24 | .card-body { |
38 | z-index: 2; | 25 | z-index: 2; |
39 | } | 26 | } |
40 | } | 27 | } |
28 | |||
29 | my-global-icon { | ||
30 | opacity: 0.12; | ||
31 | position: absolute; | ||
32 | left: 16px; | ||
33 | top: -24px; | ||
34 | width: 110px; | ||
35 | |||
36 | &.icon-bottom { | ||
37 | top: 4px; | ||
38 | } | ||
39 | } | ||
diff --git a/client/src/app/shared/shared-instance/shared-instance.module.ts b/client/src/app/shared/shared-instance/shared-instance.module.ts index 13c681ab8..dfce88e11 100644 --- a/client/src/app/shared/shared-instance/shared-instance.module.ts +++ b/client/src/app/shared/shared-instance/shared-instance.module.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | 1 | ||
2 | import { NgModule } from '@angular/core' | 2 | import { NgModule } from '@angular/core' |
3 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' |
4 | import { SharedGlobalIconModule } from '../shared-icons' | ||
4 | import { SharedMainModule } from '../shared-main/shared-main.module' | 5 | import { SharedMainModule } from '../shared-main/shared-main.module' |
5 | import { FeatureBooleanComponent } from './feature-boolean.component' | 6 | import { FeatureBooleanComponent } from './feature-boolean.component' |
6 | import { InstanceAboutAccordionComponent } from './instance-about-accordion.component' | 7 | import { InstanceAboutAccordionComponent } from './instance-about-accordion.component' |
@@ -12,6 +13,7 @@ import { InstanceService } from './instance.service' | |||
12 | @NgModule({ | 13 | @NgModule({ |
13 | imports: [ | 14 | imports: [ |
14 | SharedMainModule, | 15 | SharedMainModule, |
16 | SharedGlobalIconModule, | ||
15 | NgbAccordionModule | 17 | NgbAccordionModule |
16 | ], | 18 | ], |
17 | 19 | ||
diff --git a/client/src/app/shared/shared-main/angular/autofocus.directive.ts b/client/src/app/shared/shared-main/angular/autofocus.directive.ts index 2da492ea1..051635f45 100644 --- a/client/src/app/shared/shared-main/angular/autofocus.directive.ts +++ b/client/src/app/shared/shared-main/angular/autofocus.directive.ts | |||
@@ -7,6 +7,8 @@ export class AutofocusDirective implements AfterViewInit { | |||
7 | constructor (private host: ElementRef) { } | 7 | constructor (private host: ElementRef) { } |
8 | 8 | ||
9 | ngAfterViewInit () { | 9 | ngAfterViewInit () { |
10 | this.host.nativeElement.focus() | 10 | const el = this.host.nativeElement as HTMLElement |
11 | |||
12 | el.focus({ preventScroll: true }) | ||
11 | } | 13 | } |
12 | } | 14 | } |
diff --git a/client/src/app/shared/shared-main/angular/from-now.pipe.ts b/client/src/app/shared/shared-main/angular/from-now.pipe.ts index d62c1f88e..dc6a25e83 100644 --- a/client/src/app/shared/shared-main/angular/from-now.pipe.ts +++ b/client/src/app/shared/shared-main/angular/from-now.pipe.ts | |||
@@ -1,37 +1,51 @@ | |||
1 | import { Pipe, PipeTransform } from '@angular/core' | 1 | import { Pipe, PipeTransform } from '@angular/core' |
2 | import { prepareIcu } from '@app/helpers' | ||
2 | 3 | ||
3 | // Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site | 4 | // Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site |
4 | @Pipe({ name: 'myFromNow' }) | 5 | @Pipe({ name: 'myFromNow' }) |
5 | export class FromNowPipe implements PipeTransform { | 6 | export class FromNowPipe implements PipeTransform { |
7 | private yearICU = prepareIcu($localize`{interval, plural, =1 {1 year ago} other {{interval} years ago}}`) | ||
8 | private monthICU = prepareIcu($localize`{interval, plural, =1 {1 month ago} other {{interval} months ago}}`) | ||
9 | private weekICU = prepareIcu($localize`{interval, plural, =1 {1 week ago} other {{interval} weeks ago}}`) | ||
10 | private dayICU = prepareIcu($localize`{interval, plural, =1 {1 day ago} other {{interval} days ago}}`) | ||
11 | private hourICU = prepareIcu($localize`{interval, plural, =1 {1 hour ago} other {{interval} hours ago}}`) | ||
12 | |||
6 | transform (arg: number | Date | string) { | 13 | transform (arg: number | Date | string) { |
7 | const argDate = new Date(arg) | 14 | const argDate = new Date(arg) |
8 | const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000) | 15 | const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000) |
9 | 16 | ||
10 | let interval = Math.floor(seconds / 31536000) | 17 | let interval = Math.floor(seconds / 31536000) |
11 | if (interval > 1) return $localize`${interval} years ago` | 18 | if (interval >= 1) { |
12 | if (interval === 1) return $localize`1 year ago` | 19 | return this.yearICU({ interval }, $localize`${interval} year(s) ago`) |
20 | } | ||
13 | 21 | ||
14 | interval = Math.floor(seconds / 2419200) | 22 | interval = Math.floor(seconds / 2419200) |
15 | // 12 months = 360 days, but a year ~ 365 days | 23 | // 12 months = 360 days, but a year ~ 365 days |
16 | // Display "1 year ago" rather than "12 months ago" | 24 | // Display "1 year ago" rather than "12 months ago" |
17 | if (interval >= 12) return $localize`1 year ago` | 25 | if (interval >= 12) return $localize`1 year ago` |
18 | if (interval > 1) return $localize`${interval} months ago` | 26 | |
19 | if (interval === 1) return $localize`1 month ago` | 27 | if (interval >= 1) { |
28 | return this.monthICU({ interval }, $localize`${interval} month(s) ago`) | ||
29 | } | ||
20 | 30 | ||
21 | interval = Math.floor(seconds / 604800) | 31 | interval = Math.floor(seconds / 604800) |
22 | // 4 weeks ~ 28 days, but our month is 30 days | 32 | // 4 weeks ~ 28 days, but our month is 30 days |
23 | // Display "1 month ago" rather than "4 weeks ago" | 33 | // Display "1 month ago" rather than "4 weeks ago" |
24 | if (interval >= 4) return $localize`1 month ago` | 34 | if (interval >= 4) return $localize`1 month ago` |
25 | if (interval > 1) return $localize`${interval} weeks ago` | 35 | |
26 | if (interval === 1) return $localize`1 week ago` | 36 | if (interval >= 1) { |
37 | return this.weekICU({ interval }, $localize`${interval} week(s) ago`) | ||
38 | } | ||
27 | 39 | ||
28 | interval = Math.floor(seconds / 86400) | 40 | interval = Math.floor(seconds / 86400) |
29 | if (interval > 1) return $localize`${interval} days ago` | 41 | if (interval >= 1) { |
30 | if (interval === 1) return $localize`1 day ago` | 42 | return this.dayICU({ interval }, $localize`${interval} day(s) ago`) |
43 | } | ||
31 | 44 | ||
32 | interval = Math.floor(seconds / 3600) | 45 | interval = Math.floor(seconds / 3600) |
33 | if (interval > 1) return $localize`${interval} hours ago` | 46 | if (interval >= 1) { |
34 | if (interval === 1) return $localize`1 hour ago` | 47 | return this.hourICU({ interval }, $localize`${interval} hour(s) ago`) |
48 | } | ||
35 | 49 | ||
36 | interval = Math.floor(seconds / 60) | 50 | interval = Math.floor(seconds / 60) |
37 | if (interval >= 1) return $localize`${interval} min ago` | 51 | if (interval >= 1) return $localize`${interval} min ago` |
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html index 10dae68f0..017355bd0 100644 --- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html | |||
@@ -39,7 +39,7 @@ | |||
39 | </span> | 39 | </span> |
40 | 40 | ||
41 | <h6 | 41 | <h6 |
42 | *ngIf="!action.linkBuilder && action.isHeader" [ngClass]="{ 'with-icon': !!action.iconName }" | 42 | *ngIf="!action.linkBuilder && action.isHeader && areActionsDisplayed(actions, entry)" [ngClass]="{ 'with-icon': !!action.iconName }" |
43 | class="dropdown-header" tabindex="0" role="button" [title]="action.title || ''" (click)="action.handler(entry)" (keyup.enter)="action.handler(entry)" | 43 | class="dropdown-header" tabindex="0" role="button" [title]="action.title || ''" (click)="action.handler(entry)" (keyup.enter)="action.handler(entry)" |
44 | > | 44 | > |
45 | <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container> | 45 | <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container> |
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts index 67ac6e1aa..749773f8a 100644 --- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts | |||
@@ -48,7 +48,7 @@ export class ActionDropdownComponent<T> { | |||
48 | return actions.some(a => { | 48 | return actions.some(a => { |
49 | if (Array.isArray(a)) return this.areActionsDisplayed(a, entry) | 49 | if (Array.isArray(a)) return this.areActionsDisplayed(a, entry) |
50 | 50 | ||
51 | return a.isDisplayed === undefined || a.isDisplayed(entry) | 51 | return a.isHeader !== true && (a.isDisplayed === undefined || a.isDisplayed(entry)) |
52 | }) | 52 | }) |
53 | } | 53 | } |
54 | } | 54 | } |
diff --git a/client/src/app/shared/shared-main/buttons/button.component.html b/client/src/app/shared/shared-main/buttons/button.component.html index d1a4215e6..3e3728623 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.html +++ b/client/src/app/shared/shared-main/buttons/button.component.html | |||
@@ -1,8 +1,16 @@ | |||
1 | <span class="action-button" [ngClass]="getClasses()" [ngbTooltip]="title" tabindex="0"> | 1 | <div *ngIf="!routerLink" class="action-button" [ngClass]="classes" [ngbTooltip]="title" tabindex="0"> |
2 | <my-small-loader [loading]="loading"></my-small-loader> | 2 | <ng-container *ngTemplateOutlet="content"></ng-container> |
3 | </div> | ||
4 | |||
5 | <a *ngIf="routerLink" class="action-button" [ngClass]="classes" [ngbTooltip]="title" [routerLink]="routerLink"> | ||
6 | <ng-container *ngTemplateOutlet="content"></ng-container> | ||
7 | </a> | ||
8 | |||
9 | <ng-template #content> | ||
10 | <my-loader size="sm" [loading]="loading"></my-loader> | ||
3 | <my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon> | 11 | <my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon> |
4 | 12 | ||
5 | <span *ngIf="label" class="button-label">{{ label }}</span> | 13 | <span *ngIf="label" class="button-label">{{ label }}</span> |
6 | 14 | ||
7 | <ng-content></ng-content> | 15 | <ng-content></ng-content> |
8 | </span> | 16 | </ng-template> |
diff --git a/client/src/app/shared/shared-main/buttons/button.component.scss b/client/src/app/shared/shared-main/buttons/button.component.scss index c53b8f2e5..7f0cdf1ed 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.scss +++ b/client/src/app/shared/shared-main/buttons/button.component.scss | |||
@@ -9,17 +9,14 @@ | |||
9 | .button-label { | 9 | .button-label { |
10 | display: none; | 10 | display: none; |
11 | } | 11 | } |
12 | } | ||
13 | 12 | ||
14 | :host { | 13 | my-global-icon { |
15 | outline: none; | 14 | margin: 0 !important; |
16 | display: inline-block; | 15 | } |
17 | } | 16 | } |
18 | 17 | ||
19 | my-small-loader ::ng-deep .root { | 18 | :host { |
20 | display: inline-block; | 19 | display: inline-block; |
21 | margin: 0 3px 0 0; | ||
22 | width: 20px; | ||
23 | } | 20 | } |
24 | 21 | ||
25 | a[class$=-button], | 22 | a[class$=-button], |
@@ -30,35 +27,34 @@ span[class$=-button] { | |||
30 | } | 27 | } |
31 | 28 | ||
32 | .action-button { | 29 | .action-button { |
33 | @include peertube-button-link; | ||
34 | @include button-with-icon(21px); | ||
35 | |||
36 | width: 100%; // useful for ellipsis, allow to define a max-width on host component | 30 | width: 100%; // useful for ellipsis, allow to define a max-width on host component |
37 | 31 | ||
38 | &.icon-only { | 32 | &.has-icon { |
39 | my-global-icon { | 33 | @include button-with-icon(21px); |
40 | margin: 0; | 34 | } |
41 | } | 35 | |
36 | &.icon-only my-global-icon { | ||
37 | margin: 0 !important; | ||
42 | } | 38 | } |
43 | } | 39 | } |
44 | 40 | ||
45 | .orange-button { | 41 | .orange-button, |
42 | .grey-button { | ||
46 | @include peertube-button; | 43 | @include peertube-button; |
47 | @include orange-button; | ||
48 | } | 44 | } |
49 | 45 | ||
50 | .orange-button-link { | 46 | .orange-button-link, |
47 | .grey-button-link { | ||
51 | @include peertube-button-link; | 48 | @include peertube-button-link; |
52 | @include orange-button; | ||
53 | } | 49 | } |
54 | 50 | ||
55 | .grey-button { | 51 | .orange-button, |
56 | @include peertube-button; | 52 | .orange-button-link { |
57 | @include grey-button; | 53 | @include orange-button; |
58 | } | 54 | } |
59 | 55 | ||
56 | .grey-button, | ||
60 | .grey-button-link { | 57 | .grey-button-link { |
61 | @include peertube-button-link; | ||
62 | @include grey-button; | 58 | @include grey-button; |
63 | } | 59 | } |
64 | 60 | ||
diff --git a/client/src/app/shared/shared-main/buttons/button.component.ts b/client/src/app/shared/shared-main/buttons/button.component.ts index 52936a4d4..10d67831f 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.ts +++ b/client/src/app/shared/shared-main/buttons/button.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input, OnChanges } from '@angular/core' |
2 | import { GlobalIconName } from '@app/shared/shared-icons' | 2 | import { GlobalIconName } from '@app/shared/shared-icons' |
3 | 3 | ||
4 | @Component({ | 4 | @Component({ |
@@ -7,20 +7,24 @@ import { GlobalIconName } from '@app/shared/shared-icons' | |||
7 | templateUrl: './button.component.html' | 7 | templateUrl: './button.component.html' |
8 | }) | 8 | }) |
9 | 9 | ||
10 | export class ButtonComponent { | 10 | export class ButtonComponent implements OnChanges { |
11 | @Input() label = '' | 11 | @Input() label = '' |
12 | @Input() className = 'grey-button' | 12 | @Input() className = 'grey-button' |
13 | @Input() icon: GlobalIconName = undefined | 13 | @Input() icon: GlobalIconName = undefined |
14 | @Input() routerLink: string[] | string | ||
14 | @Input() title: string = undefined | 15 | @Input() title: string = undefined |
15 | @Input() loading = false | 16 | @Input() loading = false |
16 | @Input() disabled = false | 17 | @Input() disabled = false |
17 | @Input() responsiveLabel = false | 18 | @Input() responsiveLabel = false |
18 | 19 | ||
19 | getClasses () { | 20 | classes: { [id: string]: boolean } = {} |
20 | return { | 21 | |
22 | ngOnChanges () { | ||
23 | this.classes = { | ||
21 | [this.className]: true, | 24 | [this.className]: true, |
22 | disabled: this.disabled, | 25 | disabled: this.disabled, |
23 | 'icon-only': !this.label, | 26 | 'icon-only': !this.label, |
27 | 'has-icon': !!this.icon, | ||
24 | 'responsive-label': this.responsiveLabel | 28 | 'responsive-label': this.responsiveLabel |
25 | } | 29 | } |
26 | } | 30 | } |
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.html b/client/src/app/shared/shared-main/buttons/delete-button.component.html deleted file mode 100644 index d7a6702a7..000000000 --- a/client/src/app/shared/shared-main/buttons/delete-button.component.html +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | <span | ||
2 | class="action-button action-button-delete grey-button" | ||
3 | [ngClass]="{ 'responsive-label': responsiveLabel }" [ngbTooltip]="title" role="button" tabindex="0" | ||
4 | > | ||
5 | <my-global-icon iconName="delete" aria-hidden="true"></my-global-icon> | ||
6 | |||
7 | <span class="button-label" *ngIf="label">{{ label }}</span> | ||
8 | </span> | ||
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.ts b/client/src/app/shared/shared-main/buttons/delete-button.component.ts index 90735852c..1cab10803 100644 --- a/client/src/app/shared/shared-main/buttons/delete-button.component.ts +++ b/client/src/app/shared/shared-main/buttons/delete-button.component.ts | |||
@@ -2,17 +2,16 @@ import { Component, Input, OnInit } from '@angular/core' | |||
2 | 2 | ||
3 | @Component({ | 3 | @Component({ |
4 | selector: 'my-delete-button', | 4 | selector: 'my-delete-button', |
5 | styleUrls: [ './button.component.scss' ], | 5 | template: ` |
6 | templateUrl: './delete-button.component.html' | 6 | <my-button icon="delete" className="grey-button" [label]="label" [title]="title" [responsiveLabel]="responsiveLabel"></my-button> |
7 | ` | ||
7 | }) | 8 | }) |
8 | |||
9 | export class DeleteButtonComponent implements OnInit { | 9 | export class DeleteButtonComponent implements OnInit { |
10 | @Input() label: string | 10 | @Input() label: string |
11 | @Input() title: string | 11 | @Input() title: string |
12 | @Input() responsiveLabel = false | 12 | @Input() responsiveLabel = false |
13 | 13 | ||
14 | ngOnInit () { | 14 | ngOnInit () { |
15 | // <my-delete-button /> No label | ||
16 | if (this.label === undefined && !this.title) { | 15 | if (this.label === undefined && !this.title) { |
17 | this.title = $localize`Delete` | 16 | this.title = $localize`Delete` |
18 | } | 17 | } |
diff --git a/client/src/app/shared/shared-main/buttons/edit-button.component.html b/client/src/app/shared/shared-main/buttons/edit-button.component.html deleted file mode 100644 index 8beeee6c4..000000000 --- a/client/src/app/shared/shared-main/buttons/edit-button.component.html +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | <a | ||
2 | class="action-button action-button-edit grey-button" | ||
3 | [ngClass]="{ 'responsive-label': responsiveLabel }" [routerLink]="routerLink" [ngbTooltip]="title" | ||
4 | > | ||
5 | <my-global-icon iconName="edit" aria-hidden="true"></my-global-icon> | ||
6 | |||
7 | <span class="button-label" *ngIf="label">{{ label }}</span> | ||
8 | </a> | ||
diff --git a/client/src/app/shared/shared-main/buttons/edit-button.component.ts b/client/src/app/shared/shared-main/buttons/edit-button.component.ts index 24c8625ff..28aacbbff 100644 --- a/client/src/app/shared/shared-main/buttons/edit-button.component.ts +++ b/client/src/app/shared/shared-main/buttons/edit-button.component.ts | |||
@@ -2,8 +2,13 @@ import { Component, Input, OnInit } from '@angular/core' | |||
2 | 2 | ||
3 | @Component({ | 3 | @Component({ |
4 | selector: 'my-edit-button', | 4 | selector: 'my-edit-button', |
5 | styleUrls: [ './button.component.scss' ], | 5 | template: ` |
6 | templateUrl: './edit-button.component.html' | 6 | <my-button |
7 | icon="edit" className="grey-button-link" | ||
8 | [label]="label" [title]="title" [responsiveLabel]="responsiveLabel" | ||
9 | [routerLink]="routerLink" | ||
10 | ></my-button> | ||
11 | ` | ||
7 | }) | 12 | }) |
8 | export class EditButtonComponent implements OnInit { | 13 | export class EditButtonComponent implements OnInit { |
9 | @Input() label: string | 14 | @Input() label: string |
@@ -20,10 +25,6 @@ export class EditButtonComponent implements OnInit { | |||
20 | // <my-edit-button label /> Use default label | 25 | // <my-edit-button label /> Use default label |
21 | if (this.label === '') { | 26 | if (this.label === '') { |
22 | this.label = $localize`Update` | 27 | this.label = $localize`Update` |
23 | |||
24 | if (!this.title) { | ||
25 | this.title = this.label | ||
26 | } | ||
27 | } | 28 | } |
28 | } | 29 | } |
29 | } | 30 | } |
diff --git a/client/src/app/shared/shared-main/loaders/index.ts b/client/src/app/shared/shared-main/loaders/index.ts index a061914d5..60483727c 100644 --- a/client/src/app/shared/shared-main/loaders/index.ts +++ b/client/src/app/shared/shared-main/loaders/index.ts | |||
@@ -1,2 +1 @@ | |||
1 | export * from './loader.component' | export * from './loader.component' | |
2 | export * from './small-loader.component' | ||
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.html b/client/src/app/shared/shared-main/loaders/loader.component.html deleted file mode 100644 index ca8ed063e..000000000 --- a/client/src/app/shared/shared-main/loaders/loader.component.html +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | <div *ngIf="loading"> | ||
2 | <div class="loader"> | ||
3 | <div></div> | ||
4 | <div></div> | ||
5 | <div></div> | ||
6 | <div></div> | ||
7 | </div> | ||
8 | </div> | ||
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.scss b/client/src/app/shared/shared-main/loaders/loader.component.scss deleted file mode 100644 index b88b0db6a..000000000 --- a/client/src/app/shared/shared-main/loaders/loader.component.scss +++ /dev/null | |||
@@ -1,45 +0,0 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | |||
4 | // Thanks to https://loading.io/css/ (CC0 License) | ||
5 | |||
6 | .loader { | ||
7 | display: inline-block; | ||
8 | position: relative; | ||
9 | width: 50px; | ||
10 | height: 50px; | ||
11 | } | ||
12 | |||
13 | .loader div { | ||
14 | box-sizing: border-box; | ||
15 | display: block; | ||
16 | position: absolute; | ||
17 | width: 44px; | ||
18 | height: 44px; | ||
19 | margin: 6px; | ||
20 | border: 4px solid; | ||
21 | border-radius: 50%; | ||
22 | animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; | ||
23 | border-color: #999999 transparent transparent; | ||
24 | } | ||
25 | |||
26 | .loader div:nth-child(1) { | ||
27 | animation-delay: -0.45s; | ||
28 | } | ||
29 | |||
30 | .loader div:nth-child(2) { | ||
31 | animation-delay: -0.3s; | ||
32 | } | ||
33 | |||
34 | .loader div:nth-child(3) { | ||
35 | animation-delay: -0.15s; | ||
36 | } | ||
37 | |||
38 | @keyframes loader { | ||
39 | 0% { | ||
40 | transform: rotate(0deg); | ||
41 | } | ||
42 | 100% { | ||
43 | transform: rotate(360deg); | ||
44 | } | ||
45 | } | ||
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.ts b/client/src/app/shared/shared-main/loaders/loader.component.ts index e3b1eea3a..bd038f8b5 100644 --- a/client/src/app/shared/shared-main/loaders/loader.component.ts +++ b/client/src/app/shared/shared-main/loaders/loader.component.ts | |||
@@ -2,9 +2,27 @@ import { Component, Input } from '@angular/core' | |||
2 | 2 | ||
3 | @Component({ | 3 | @Component({ |
4 | selector: 'my-loader', | 4 | selector: 'my-loader', |
5 | styleUrls: [ './loader.component.scss' ], | 5 | template: `<div *ngIf="loading" class="spinner-border" [ngStyle]="getStyle()" role="status"></div>` |
6 | templateUrl: './loader.component.html' | ||
7 | }) | 6 | }) |
8 | export class LoaderComponent { | 7 | export class LoaderComponent { |
9 | @Input() loading: boolean | 8 | @Input() loading: boolean |
9 | @Input() size: 'sm' | 'xl' | ||
10 | |||
11 | private readonly sizes = { | ||
12 | sm: { | ||
13 | width: '1rem', | ||
14 | height: '1rem', | ||
15 | 'border-width': '0.15rem' | ||
16 | }, | ||
17 | xl: { | ||
18 | width: '3rem', | ||
19 | height: '3rem' | ||
20 | } | ||
21 | } | ||
22 | |||
23 | getStyle () { | ||
24 | if (!this.size) return undefined | ||
25 | |||
26 | return this.sizes[this.size] | ||
27 | } | ||
10 | } | 28 | } |
diff --git a/client/src/app/shared/shared-main/loaders/small-loader.component.html b/client/src/app/shared/shared-main/loaders/small-loader.component.html deleted file mode 100644 index 7886f8918..000000000 --- a/client/src/app/shared/shared-main/loaders/small-loader.component.html +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | <div class="root" *ngIf="loading"> | ||
2 | <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div> | ||
3 | </div> | ||
diff --git a/client/src/app/shared/shared-main/loaders/small-loader.component.ts b/client/src/app/shared/shared-main/loaders/small-loader.component.ts deleted file mode 100644 index 191877f14..000000000 --- a/client/src/app/shared/shared-main/loaders/small-loader.component.ts +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-small-loader', | ||
5 | styleUrls: [ ], | ||
6 | templateUrl: './small-loader.component.html' | ||
7 | }) | ||
8 | |||
9 | export class SmallLoaderComponent { | ||
10 | @Input() loading: boolean | ||
11 | } | ||
diff --git a/client/src/app/shared/shared-main/misc/channels-setup-message.component.html b/client/src/app/shared/shared-main/misc/channels-setup-message.component.html index 3fe888a35..539df06bd 100644 --- a/client/src/app/shared/shared-main/misc/channels-setup-message.component.html +++ b/client/src/app/shared/shared-main/misc/channels-setup-message.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div *ngIf="hasChannelNotConfigured()" class="channels-setup-message alert alert-info"> | 1 | <div *ngIf="hasChannelNotConfigured()" class="channels-setup-message alert pt-alert-primary"> |
2 | <my-global-icon iconName="tip"></my-global-icon> | 2 | <my-global-icon iconName="tip"></my-global-icon> |
3 | 3 | ||
4 | <div> | 4 | <div> |
diff --git a/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss b/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss index 7dcba2ca5..2aa176e1b 100644 --- a/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss +++ b/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss | |||
@@ -5,28 +5,24 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | align-items: center; | 6 | align-items: center; |
7 | justify-content: center; | 7 | justify-content: center; |
8 | } | ||
8 | 9 | ||
9 | my-global-icon { | 10 | my-global-icon { |
10 | width: 32px; | 11 | @include apply-svg-color(pvar(--mainColor)); |
11 | align-self: flex-start; | ||
12 | 12 | ||
13 | ::ng-deep { | 13 | width: 32px; |
14 | svg { | 14 | align-self: flex-start; |
15 | fill: #0c5460; | ||
16 | } | ||
17 | } | ||
18 | 15 | ||
19 | + div { | 16 | + div { |
20 | margin-left: 10px; | 17 | margin-left: 10px; |
21 | text-align: center; | 18 | text-align: center; |
19 | } | ||
20 | } | ||
22 | 21 | ||
23 | a.channels-settings-link { | 22 | .channels-settings-link { |
24 | @include peertube-button-link; | 23 | @include peertube-button-link; |
25 | @include grey-button; | 24 | @include grey-button; |
26 | 25 | ||
27 | height: fit-content; | 26 | height: fit-content; |
28 | margin-top: 10px; | 27 | margin-top: 10px; |
29 | } | ||
30 | } | ||
31 | } | ||
32 | } | 28 | } |
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.html b/client/src/app/shared/shared-main/misc/list-overflow.component.html index b2e0982f1..6f29eaefa 100644 --- a/client/src/app/shared/shared-main/misc/list-overflow.component.html +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.html | |||
@@ -1,18 +1,22 @@ | |||
1 | <div #itemsParent class="d-flex align-items-center text-nowrap w-100 list-overflow-parent"> | 1 | <div #itemsParent class="list-overflow-parent"> |
2 | <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id"> | 2 | <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id"> |
3 | <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container> | 3 | <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container> |
4 | </span> | 4 | </span> |
5 | 5 | ||
6 | <ng-container *ngIf="isMenuDisplayed()"> | 6 | <ng-container *ngIf="isMenuDisplayed()"> |
7 | <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()"> | 7 | <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()"> |
8 | <span class="glyphicon glyphicon-chevron-down"></span> | 8 | <span class="chevron-down"></span> |
9 | </button> | 9 | </button> |
10 | 10 | ||
11 | <div *ngIf="!isInMobileView" class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)"> | 11 | <div |
12 | *ngIf="!isInMobileView" class="list-overflow-menu" | ||
13 | ngbDropdown container="body" #dropdown="ngbDropdown" | ||
14 | (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)" | ||
15 | > | ||
12 | <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ 'route-active': active }" | 16 | <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ 'route-active': active }" |
13 | ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button" | 17 | ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button" |
14 | > | 18 | > |
15 | <span class="glyphicon glyphicon-chevron-down"></span> | 19 | <span class="chevron-down"></span> |
16 | </button> | 20 | </button> |
17 | 21 | ||
18 | <div ngbDropdownMenu> | 22 | <div ngbDropdownMenu> |
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.scss b/client/src/app/shared/shared-main/misc/list-overflow.component.scss index 19c055fd3..b06418568 100644 --- a/client/src/app/shared/shared-main/misc/list-overflow.component.scss +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.scss | |||
@@ -7,6 +7,9 @@ | |||
7 | 7 | ||
8 | .list-overflow-parent { | 8 | .list-overflow-parent { |
9 | overflow: hidden; | 9 | overflow: hidden; |
10 | display: flex; | ||
11 | // For the menu icon | ||
12 | max-width: calc(100vw - 30px); | ||
10 | } | 13 | } |
11 | 14 | ||
12 | .list-overflow-menu { | 15 | .list-overflow-menu { |
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.ts b/client/src/app/shared/shared-main/misc/list-overflow.component.ts index fbc481093..541991f74 100644 --- a/client/src/app/shared/shared-main/misc/list-overflow.component.ts +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.ts | |||
@@ -15,6 +15,9 @@ import { | |||
15 | } from '@angular/core' | 15 | } from '@angular/core' |
16 | import { ScreenService } from '@app/core' | 16 | import { ScreenService } from '@app/core' |
17 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' | 17 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' |
18 | import * as debug from 'debug' | ||
19 | |||
20 | const logger = debug('peertube:main:ListOverflowItem') | ||
18 | 21 | ||
19 | export interface ListOverflowItem { | 22 | export interface ListOverflowItem { |
20 | label: string | 23 | label: string |
@@ -37,7 +40,6 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV | |||
37 | 40 | ||
38 | showItemsUntilIndexExcluded: number | 41 | showItemsUntilIndexExcluded: number |
39 | active = false | 42 | active = false |
40 | isInTouchScreen = false | ||
41 | isInMobileView = false | 43 | isInMobileView = false |
42 | 44 | ||
43 | private openedOnHover = false | 45 | private openedOnHover = false |
@@ -58,13 +60,14 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV | |||
58 | 60 | ||
59 | @HostListener('window:resize') | 61 | @HostListener('window:resize') |
60 | onWindowResize () { | 62 | onWindowResize () { |
61 | this.isInTouchScreen = !!this.screenService.isInTouchScreen() | ||
62 | this.isInMobileView = !!this.screenService.isInMobileView() | 63 | this.isInMobileView = !!this.screenService.isInMobileView() |
63 | 64 | ||
64 | const parentWidth = this.parent.nativeElement.getBoundingClientRect().width | 65 | const parentWidth = this.parent.nativeElement.getBoundingClientRect().width |
65 | let showItemsUntilIndexExcluded: number | 66 | let showItemsUntilIndexExcluded: number |
66 | let accWidth = 0 | 67 | let accWidth = 0 |
67 | 68 | ||
69 | logger('Parent width is %d', parentWidth) | ||
70 | |||
68 | for (const [ index, el ] of this.itemsRendered.toArray().entries()) { | 71 | for (const [ index, el ] of this.itemsRendered.toArray().entries()) { |
69 | accWidth += el.nativeElement.getBoundingClientRect().width | 72 | accWidth += el.nativeElement.getBoundingClientRect().width |
70 | if (showItemsUntilIndexExcluded === undefined) { | 73 | if (showItemsUntilIndexExcluded === undefined) { |
@@ -76,6 +79,8 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV | |||
76 | e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' | 79 | e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' |
77 | } | 80 | } |
78 | 81 | ||
82 | logger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded) | ||
83 | |||
79 | this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded | 84 | this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded |
80 | this.cdr.markForCheck() | 85 | this.cdr.markForCheck() |
81 | } | 86 | } |
diff --git a/client/src/app/shared/shared-main/misc/simple-search-input.component.html b/client/src/app/shared/shared-main/misc/simple-search-input.component.html index 1e2f6c6a9..386d26116 100644 --- a/client/src/app/shared/shared-main/misc/simple-search-input.component.html +++ b/client/src/app/shared/shared-main/misc/simple-search-input.component.html | |||
@@ -1,17 +1,10 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | <div class="input-group has-feedback has-clear"> | 2 | <div class="input-group has-clear"> |
3 | <input | 3 | <input #ref type="text" class="last-in-group" |
4 | #ref | 4 | [(ngModel)]="value" (keyup.enter)="sendSearch()" [hidden]="!inputShown" [name]="name" [placeholder]="placeholder" |
5 | type="text" | ||
6 | [(ngModel)]="value" | ||
7 | (keyup.enter)="sendSearch()" | ||
8 | [hidden]="!inputShown" | ||
9 | [name]="name" | ||
10 | [placeholder]="placeholder" | ||
11 | > | 5 | > |
12 | 6 | ||
13 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="onResetFilter()"></a> | 7 | <my-global-icon iconName="cross" role="button" class="form-control-clear" title="Clear filter" i18n-title (click)="onResetFilter()"></my-global-icon> |
14 | <span class="sr-only" i18n>Clear filters</span> | ||
15 | </div> | 8 | </div> |
16 | 9 | ||
17 | <my-global-icon iconName="search" aria-label="Search" role="button" (click)="onIconClick()" [title]="iconTitle"></my-global-icon> | 10 | <my-global-icon iconName="search" aria-label="Search" role="button" (click)="onIconClick()" [title]="iconTitle"></my-global-icon> |
diff --git a/client/src/app/shared/shared-main/misc/simple-search-input.component.scss b/client/src/app/shared/shared-main/misc/simple-search-input.component.scss index d5fcff760..ee0f7a8d2 100644 --- a/client/src/app/shared/shared-main/misc/simple-search-input.component.scss +++ b/client/src/app/shared/shared-main/misc/simple-search-input.component.scss | |||
@@ -5,7 +5,7 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | } | 6 | } |
7 | 7 | ||
8 | my-global-icon { | 8 | .root > my-global-icon { |
9 | @include margin-left(10px); | 9 | @include margin-left(10px); |
10 | 10 | ||
11 | height: 28px; | 11 | height: 28px; |
@@ -25,3 +25,7 @@ input { | |||
25 | box-shadow: 0 0 5px 0 #a5a5a5; | 25 | box-shadow: 0 0 5px 0 #a5a5a5; |
26 | } | 26 | } |
27 | } | 27 | } |
28 | |||
29 | .input-group > my-global-icon { | ||
30 | width: 20px; | ||
31 | } | ||
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html index d884e75b2..d96fdbdc6 100644 --- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html +++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html | |||
@@ -1,7 +1,13 @@ | |||
1 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }"> | 1 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }"> |
2 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> | 2 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> |
3 | 3 | ||
4 | <a *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings" #routerLink (click)="onActiveLinkScrollToTop(routerLink)">{{ menuEntry.label }}</a> | 4 | <a |
5 | *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" class="sub-menu-entry" | ||
6 | [routerLink]="menuEntry.routerLink" routerLinkActive="active" #routerLink | ||
7 | (click)="onActiveLinkScrollToTop(routerLink)" | ||
8 | > | ||
9 | {{ menuEntry.label }} | ||
10 | </a> | ||
5 | 11 | ||
6 | <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry" | 12 | <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry" |
7 | #dropdown="ngbDropdown" autoClose="true" container="body"> | 13 | #dropdown="ngbDropdown" autoClose="true" container="body"> |
@@ -10,7 +16,7 @@ | |||
10 | tabindex=0 | 16 | tabindex=0 |
11 | [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" | 17 | [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" |
12 | (click)="openModal(id)" (keydown.enter)="openModal(id)" | 18 | (click)="openModal(id)" (keydown.enter)="openModal(id)" |
13 | role="button" class="title-page title-page-settings"> | 19 | role="button" class="sub-menu-entry"> |
14 | <ng-container i18n>{{ menuEntry.label }}</ng-container> | 20 | <ng-container i18n>{{ menuEntry.label }}</ng-container> |
15 | </span> | 21 | </span> |
16 | 22 | ||
@@ -19,7 +25,7 @@ | |||
19 | tabindex=0 | 25 | tabindex=0 |
20 | [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor | 26 | [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor |
21 | (click)="dropdownAnchorClicked(dropdown)" (keydown.enter)="dropdownAnchorClicked(dropdown)" | 27 | (click)="dropdownAnchorClicked(dropdown)" (keydown.enter)="dropdownAnchorClicked(dropdown)" |
22 | role="button" class="title-page title-page-settings" | 28 | role="button" class="sub-menu-entry" |
23 | > | 29 | > |
24 | <ng-container i18n>{{ menuEntry.label }}</ng-container> | 30 | <ng-container i18n>{{ menuEntry.label }}</ng-container> |
25 | </span> | 31 | </span> |
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index 5629640bc..89f43239f 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts | |||
@@ -34,7 +34,7 @@ import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditBu | |||
34 | import { CustomPageService } from './custom-page' | 34 | import { CustomPageService } from './custom-page' |
35 | import { DateToggleComponent } from './date' | 35 | import { DateToggleComponent } from './date' |
36 | import { FeedComponent } from './feeds' | 36 | import { FeedComponent } from './feeds' |
37 | import { LoaderComponent, SmallLoaderComponent } from './loaders' | 37 | import { LoaderComponent } from './loaders' |
38 | import { | 38 | import { |
39 | ChannelsSetupMessageComponent, | 39 | ChannelsSetupMessageComponent, |
40 | HelpComponent, | 40 | HelpComponent, |
@@ -97,7 +97,6 @@ import { VideoChannelService } from './video-channel' | |||
97 | FeedComponent, | 97 | FeedComponent, |
98 | 98 | ||
99 | LoaderComponent, | 99 | LoaderComponent, |
100 | SmallLoaderComponent, | ||
101 | 100 | ||
102 | ChannelsSetupMessageComponent, | 101 | ChannelsSetupMessageComponent, |
103 | HelpComponent, | 102 | HelpComponent, |
@@ -157,7 +156,6 @@ import { VideoChannelService } from './video-channel' | |||
157 | FeedComponent, | 156 | FeedComponent, |
158 | 157 | ||
159 | LoaderComponent, | 158 | LoaderComponent, |
160 | SmallLoaderComponent, | ||
161 | 159 | ||
162 | ChannelsSetupMessageComponent, | 160 | ChannelsSetupMessageComponent, |
163 | HelpComponent, | 161 | HelpComponent, |
diff --git a/client/src/app/shared/shared-main/users/user-quota.component.html b/client/src/app/shared/shared-main/users/user-quota.component.html index dd1fc20d0..0e0d38c2a 100644 --- a/client/src/app/shared/shared-main/users/user-quota.component.html +++ b/client/src/app/shared/shared-main/users/user-quota.component.html | |||
@@ -12,7 +12,7 @@ | |||
12 | <div *ngIf="hasDailyQuota()" class="mt-3"> | 12 | <div *ngIf="hasDailyQuota()" class="mt-3"> |
13 | <label class="user-quota-title" tabindex="0" i18n>Daily video quota</label> | 13 | <label class="user-quota-title" tabindex="0" i18n>Daily video quota</label> |
14 | <div class="progress" tabindex="0" [ngbTooltip]="titleVideoQuotaDaily()"> | 14 | <div class="progress" tabindex="0" [ngbTooltip]="titleVideoQuotaDaily()"> |
15 | <div class="progress-bar secondary" role="progressbar" [style]="{ width: userVideoQuotaDailyPercentage + '%' }" | 15 | <div class="progress-bar" role="progressbar" [style]="{ width: userVideoQuotaDailyPercentage + '%' }" |
16 | [attr.aria-valuenow]="userVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.videoQuotaDaily"></div> | 16 | [attr.aria-valuenow]="userVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.videoQuotaDaily"></div> |
17 | <span>{{ userVideoQuotaUsedDaily | bytes: 1 }}</span> | 17 | <span>{{ userVideoQuotaUsedDaily | bytes: 1 }}</span> |
18 | <span>{{ userVideoQuotaDaily }}</span> | 18 | <span>{{ userVideoQuotaDaily }}</span> |
diff --git a/client/src/app/shared/shared-main/users/user-quota.component.scss b/client/src/app/shared/shared-main/users/user-quota.component.scss index 70571bde6..f3e86ce78 100644 --- a/client/src/app/shared/shared-main/users/user-quota.component.scss +++ b/client/src/app/shared/shared-main/users/user-quota.component.scss | |||
@@ -1,11 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | .user-quota { | 4 | .user-quota { |
10 | label { | 5 | label { |
11 | @include margin-right(5px); | 6 | @include margin-right(5px); |
diff --git a/client/src/app/shared/shared-main/video-caption/video-caption.service.ts b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts index 00ebe5bc6..0f3afd116 100644 --- a/client/src/app/shared/shared-main/video-caption/video-caption.service.ts +++ b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts | |||
@@ -18,7 +18,7 @@ export class VideoCaptionService { | |||
18 | private restExtractor: RestExtractor | 18 | private restExtractor: RestExtractor |
19 | ) {} | 19 | ) {} |
20 | 20 | ||
21 | listCaptions (videoId: number | string): Observable<ResultList<VideoCaption>> { | 21 | listCaptions (videoId: string): Observable<ResultList<VideoCaption>> { |
22 | return this.authHttp.get<ResultList<VideoCaption>>(`${VideoService.BASE_VIDEO_URL}/${videoId}/captions`) | 22 | return this.authHttp.get<ResultList<VideoCaption>>(`${VideoService.BASE_VIDEO_URL}/${videoId}/captions`) |
23 | .pipe( | 23 | .pipe( |
24 | switchMap(captionsResult => { | 24 | switchMap(captionsResult => { |
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts index 32376bf62..62bd94349 100644 --- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts | |||
@@ -27,6 +27,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel { | |||
27 | videosCount?: number | 27 | videosCount?: number |
28 | 28 | ||
29 | viewsPerDay?: ViewsPerDate[] | 29 | viewsPerDay?: ViewsPerDate[] |
30 | totalViews?: number | ||
30 | 31 | ||
31 | static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) { | 32 | static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) { |
32 | return Actor.GET_ACTOR_AVATAR_URL(actor, size) | 33 | return Actor.GET_ACTOR_AVATAR_URL(actor, size) |
@@ -74,6 +75,10 @@ export class VideoChannel extends Actor implements ServerVideoChannel { | |||
74 | this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date) })) | 75 | this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date) })) |
75 | } | 76 | } |
76 | 77 | ||
78 | if (hash.totalViews !== null && hash.totalViews !== undefined) { | ||
79 | this.totalViews = hash.totalViews | ||
80 | } | ||
81 | |||
77 | if (hash.ownerAccount) { | 82 | if (hash.ownerAccount) { |
78 | this.ownerAccount = hash.ownerAccount | 83 | this.ownerAccount = hash.ownerAccount |
79 | this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) | 84 | this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) |
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index 022bb95ad..2e4ab87d7 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { AuthUser } from '@app/core' | 1 | import { AuthUser } from '@app/core' |
2 | import { User } from '@app/core/users/user.model' | 2 | import { User } from '@app/core/users/user.model' |
3 | import { durationToString, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' | 3 | import { durationToString, prepareIcu, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' |
4 | import { Actor } from '@app/shared/shared-main/account/actor.model' | 4 | import { Actor } from '@app/shared/shared-main/account/actor.model' |
5 | import { buildVideoWatchPath } from '@shared/core-utils' | 5 | import { buildVideoWatchPath } from '@shared/core-utils' |
6 | import { peertubeTranslate } from '@shared/core-utils/i18n' | 6 | import { peertubeTranslate } from '@shared/core-utils/i18n' |
@@ -19,6 +19,9 @@ import { | |||
19 | } from '@shared/models' | 19 | } from '@shared/models' |
20 | 20 | ||
21 | export class Video implements VideoServerModel { | 21 | export class Video implements VideoServerModel { |
22 | private static readonly viewsICU = prepareIcu($localize`{views, plural, =0 {No view} =1 {1 view} other {{views} views}}`) | ||
23 | private static readonly viewersICU = prepareIcu($localize`{viewers, plural, =0 {No viewers} =1 {1 viewer} other {{viewers} viewers}}`) | ||
24 | |||
22 | byVideoChannel: string | 25 | byVideoChannel: string |
23 | byAccount: string | 26 | byAccount: string |
24 | 27 | ||
@@ -269,12 +272,10 @@ export class Video implements VideoServerModel { | |||
269 | } | 272 | } |
270 | 273 | ||
271 | getExactNumberOfViews () { | 274 | getExactNumberOfViews () { |
272 | if (this.views < 1000) return '' | ||
273 | |||
274 | if (this.isLive) { | 275 | if (this.isLive) { |
275 | return $localize`${this.views} viewers` | 276 | return Video.viewersICU({ viewers: this.viewers }, $localize`${this.viewers} viewer(s)`) |
276 | } | 277 | } |
277 | 278 | ||
278 | return $localize`${this.views} views` | 279 | return Video.viewsICU({ views: this.views }, $localize`{${this.views} view(s)}`) |
279 | } | 280 | } |
280 | } | 281 | } |
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 9efa1a24e..4fbc4f7f6 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { from, Observable } from 'rxjs' | 2 | import { from, Observable, of } from 'rxjs' |
3 | import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators' | 3 | import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators' |
4 | import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' | 4 | import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' |
5 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
@@ -24,6 +24,7 @@ import { | |||
24 | VideoTranscodingCreate, | 24 | VideoTranscodingCreate, |
25 | VideoUpdate | 25 | VideoUpdate |
26 | } from '@shared/models' | 26 | } from '@shared/models' |
27 | import { VideoSource } from '@shared/models/videos/video-source' | ||
27 | import { environment } from '../../../../environments/environment' | 28 | import { environment } from '../../../../environments/environment' |
28 | import { Account } from '../account/account.model' | 29 | import { Account } from '../account/account.model' |
29 | import { AccountService } from '../account/account.service' | 30 | import { AccountService } from '../account/account.service' |
@@ -323,19 +324,33 @@ export class VideoService { | |||
323 | ) | 324 | ) |
324 | } | 325 | } |
325 | 326 | ||
326 | setVideoLike (id: number) { | 327 | getSource (videoId: number) { |
328 | return this.authHttp | ||
329 | .get<{ source: VideoSource }>(VideoService.BASE_VIDEO_URL + '/' + videoId + '/source') | ||
330 | .pipe( | ||
331 | catchError(err => { | ||
332 | if (err.status === 404) { | ||
333 | return of(undefined) | ||
334 | } | ||
335 | |||
336 | this.restExtractor.handleError(err) | ||
337 | }) | ||
338 | ) | ||
339 | } | ||
340 | |||
341 | setVideoLike (id: string) { | ||
327 | return this.setVideoRate(id, 'like') | 342 | return this.setVideoRate(id, 'like') |
328 | } | 343 | } |
329 | 344 | ||
330 | setVideoDislike (id: number) { | 345 | setVideoDislike (id: string) { |
331 | return this.setVideoRate(id, 'dislike') | 346 | return this.setVideoRate(id, 'dislike') |
332 | } | 347 | } |
333 | 348 | ||
334 | unsetVideoLike (id: number) { | 349 | unsetVideoLike (id: string) { |
335 | return this.setVideoRate(id, 'none') | 350 | return this.setVideoRate(id, 'none') |
336 | } | 351 | } |
337 | 352 | ||
338 | getUserVideoRating (id: number) { | 353 | getUserVideoRating (id: string) { |
339 | const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating' | 354 | const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating' |
340 | 355 | ||
341 | return this.authHttp.get<UserVideoRate>(url) | 356 | return this.authHttp.get<UserVideoRate>(url) |
@@ -451,7 +466,7 @@ export class VideoService { | |||
451 | } | 466 | } |
452 | } | 467 | } |
453 | 468 | ||
454 | private setVideoRate (id: number, rateType: UserVideoRateType) { | 469 | private setVideoRate (id: string, rateType: UserVideoRateType) { |
455 | const url = `${VideoService.BASE_VIDEO_URL}/${id}/rate` | 470 | const url = `${VideoService.BASE_VIDEO_URL}/${id}/rate` |
456 | const body: UserVideoRateUpdate = { | 471 | const body: UserVideoRateUpdate = { |
457 | rating: rateType | 472 | rating: rateType |
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 index feac707c2..fd3709676 100644 --- a/client/src/app/shared/shared-moderation/account-block-badges.component.html +++ b/client/src/app/shared/shared-moderation/account-block-badges.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span> | 1 | <span *ngIf="account.mutedByUser" class="pt-badge badge-danger" i18n>Muted</span> |
2 | <span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span> | 2 | <span *ngIf="account.mutedServerByUser" class="pt-badge badge-danger" i18n>Instance muted</span> |
3 | <span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span> | 3 | <span *ngIf="account.mutedByInstance" class="pt-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> | 4 | <span *ngIf="account.mutedServerByInstance" class="pt-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 index ccc3666aa..301d8305e 100644 --- a/client/src/app/shared/shared-moderation/account-block-badges.component.scss +++ b/client/src/app/shared/shared-moderation/account-block-badges.component.scss | |||
@@ -1,9 +1,8 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .badge { | 4 | .pt-badge { |
5 | @include margin-right(10px); | 5 | @include margin-right(10px); |
6 | 6 | ||
7 | height: fit-content; | ||
8 | font-size: 12px; | 7 | font-size: 12px; |
9 | } | 8 | } |
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.html b/client/src/app/shared/shared-moderation/account-blocklist.component.html index a4f81d824..fef24cfdf 100644 --- a/client/src/app/shared/shared-moderation/account-blocklist.component.html +++ b/client/src/app/shared/shared-moderation/account-blocklist.component.html | |||
@@ -11,7 +11,7 @@ | |||
11 | > | 11 | > |
12 | <ng-template pTemplate="caption"> | 12 | <ng-template pTemplate="caption"> |
13 | <div class="caption"> | 13 | <div class="caption"> |
14 | <div class="ml-auto"> | 14 | <div class="ms-auto"> |
15 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> | 15 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> |
16 | </div> | 16 | </div> |
17 | </div> | 17 | </div> |
@@ -33,7 +33,7 @@ | |||
33 | <td> | 33 | <td> |
34 | <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> | 34 | <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> |
35 | <div class="chip two-lines"> | 35 | <div class="chip two-lines"> |
36 | <my-actor-avatar [account]="accountBlock.blockedAccount" size="32"></my-actor-avatar> | 36 | <my-actor-avatar [actor]="accountBlock.blockedAccount" actorType="account" size="32"></my-actor-avatar> |
37 | <div> | 37 | <div> |
38 | {{ accountBlock.blockedAccount.displayName }} | 38 | {{ accountBlock.blockedAccount.displayName }} |
39 | <span class="muted">{{ accountBlock.blockedAccount.nameWithHost }}</span> | 39 | <span class="muted">{{ accountBlock.blockedAccount.nameWithHost }}</span> |
@@ -48,7 +48,7 @@ | |||
48 | 48 | ||
49 | <ng-template pTemplate="emptymessage"> | 49 | <ng-template pTemplate="emptymessage"> |
50 | <tr> | 50 | <tr> |
51 | <td colspan="6"> | 51 | <td colspan="3"> |
52 | <div class="no-results"> | 52 | <div class="no-results"> |
53 | <ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container> | 53 | <ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container> |
54 | <ng-container *ngIf="!search" i18n>No account found.</ng-container> | 54 | <ng-container *ngIf="!search" i18n>No account found.</ng-container> |
diff --git a/client/src/app/shared/shared-moderation/report-modals/report.component.html b/client/src/app/shared/shared-moderation/report-modals/report.component.html index 6c99180ef..8e0b0993c 100644 --- a/client/src/app/shared/shared-moderation/report-modals/report.component.html +++ b/client/src/app/shared/shared-moderation/report-modals/report.component.html | |||
@@ -8,11 +8,11 @@ | |||
8 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> | 8 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> |
9 | 9 | ||
10 | <div class="row"> | 10 | <div class="row"> |
11 | <div class="col-5 form-group"> | 11 | <div class="col-5"> |
12 | 12 | ||
13 | <label i18n for="reportPredefinedReasons">What is the issue?</label> | 13 | <label i18n for="reportPredefinedReasons">What is the issue?</label> |
14 | 14 | ||
15 | <div class="ml-2 mt-2 d-flex flex-column"> | 15 | <div class="ms-2 mt-2 d-flex flex-column"> |
16 | <ng-container formGroupName="predefinedReasons"> | 16 | <ng-container formGroupName="predefinedReasons"> |
17 | 17 | ||
18 | <div class="form-group" *ngFor="let reason of predefinedReasons"> | 18 | <div class="form-group" *ngFor="let reason of predefinedReasons"> |
@@ -29,7 +29,6 @@ | |||
29 | 29 | ||
30 | </ng-container> | 30 | </ng-container> |
31 | </div> | 31 | </div> |
32 | |||
33 | </div> | 32 | </div> |
34 | 33 | ||
35 | <div class="col-7"> | 34 | <div class="col-7"> |
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html index afac108fc..51ca0b9d6 100644 --- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html | |||
@@ -8,38 +8,32 @@ | |||
8 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> | 8 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> |
9 | 9 | ||
10 | <div class="row"> | 10 | <div class="row"> |
11 | <div class="col-5 form-group"> | 11 | <div class="col-12 col-md-5"> |
12 | <label i18n for="reportPredefinedReasons">What is the issue?</label> | ||
12 | 13 | ||
13 | <label i18n for="reportPredefinedReasons">What is the issue?</label> | 14 | <div class="ms-2 mt-2 d-flex flex-column"> |
15 | <ng-container formGroupName="predefinedReasons"> | ||
14 | 16 | ||
15 | <div class="ml-2 mt-2 d-flex flex-column"> | 17 | <div class="form-group" *ngFor="let reason of predefinedReasons"> |
16 | <ng-container formGroupName="predefinedReasons"> | 18 | <my-peertube-checkbox [inputName]="reason.id" [formControlName]="reason.id" [labelText]="reason.label"> |
19 | <ng-template *ngIf="reason.help" ptTemplate="help"> | ||
20 | <div [innerHTML]="reason.help"></div> | ||
21 | </ng-template> | ||
17 | 22 | ||
18 | <div class="form-group" *ngFor="let reason of predefinedReasons"> | 23 | <ng-container *ngIf="reason.description" ngProjectAs="description"> |
19 | <my-peertube-checkbox [inputName]="reason.id" [formControlName]="reason.id" [labelText]="reason.label"> | 24 | <div [innerHTML]="reason.description"></div> |
20 | <ng-template *ngIf="reason.help" ptTemplate="help"> | 25 | </ng-container> |
21 | <div [innerHTML]="reason.help"></div> | 26 | </my-peertube-checkbox> |
22 | </ng-template> | 27 | </div> |
23 | |||
24 | <ng-container *ngIf="reason.description" ngProjectAs="description"> | ||
25 | <div [innerHTML]="reason.description"></div> | ||
26 | </ng-container> | ||
27 | </my-peertube-checkbox> | ||
28 | </div> | ||
29 | |||
30 | </ng-container> | ||
31 | </div> | ||
32 | 28 | ||
29 | </ng-container> | ||
30 | </div> | ||
33 | </div> | 31 | </div> |
34 | 32 | ||
35 | <div class="col-7"> | 33 | <div class="col-12 col-md-7"> |
36 | <div class="row justify-content-center"> | 34 | <my-embed [video]="video"></my-embed> |
37 | <div class="col-12 col-lg-9 mb-2"> | ||
38 | <my-embed [video]="video"></my-embed> | ||
39 | </div> | ||
40 | </div> | ||
41 | 35 | ||
42 | <div class="mb-1 start-at" formGroupName="timestamp"> | 36 | <div class="mb-1 mt-3 start-at" formGroupName="timestamp"> |
43 | <my-peertube-checkbox | 37 | <my-peertube-checkbox |
44 | formControlName="hasStart" | 38 | formControlName="hasStart" |
45 | i18n-labelText labelText="Start at" | 39 | i18n-labelText labelText="Start at" |
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.html b/client/src/app/shared/shared-moderation/server-blocklist.component.html index 1a320e9a4..bc47bf26f 100644 --- a/client/src/app/shared/shared-moderation/server-blocklist.component.html +++ b/client/src/app/shared/shared-moderation/server-blocklist.component.html | |||
@@ -19,7 +19,7 @@ | |||
19 | </a> | 19 | </a> |
20 | </div> | 20 | </div> |
21 | 21 | ||
22 | <div class="ml-auto"> | 22 | <div class="ms-auto"> |
23 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> | 23 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> |
24 | </div> | 24 | </div> |
25 | </div> | 25 | </div> |
@@ -41,7 +41,7 @@ | |||
41 | <td> | 41 | <td> |
42 | <a [href]="'https://' + serverBlock.blockedServer.host" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> | 42 | <a [href]="'https://' + serverBlock.blockedServer.host" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> |
43 | {{ serverBlock.blockedServer.host }} | 43 | {{ serverBlock.blockedServer.host }} |
44 | <span class="glyphicon glyphicon-new-window"></span> | 44 | <my-global-icon iconName="external-link"></my-global-icon> |
45 | </a> | 45 | </a> |
46 | </td> | 46 | </td> |
47 | <td>{{ serverBlock.createdAt | date: 'short' }}</td> | 47 | <td>{{ serverBlock.createdAt | date: 'short' }}</td> |
@@ -50,7 +50,7 @@ | |||
50 | 50 | ||
51 | <ng-template pTemplate="emptymessage"> | 51 | <ng-template pTemplate="emptymessage"> |
52 | <tr> | 52 | <tr> |
53 | <td colspan="6"> | 53 | <td colspan="3"> |
54 | <div class="no-results"> | 54 | <div class="no-results"> |
55 | <ng-container *ngIf="search" i18n>No server found matching current filters.</ng-container> | 55 | <ng-container *ngIf="search" i18n>No server found matching current filters.</ng-container> |
56 | <ng-container *ngIf="!search" i18n>No server found.</ng-container> | 56 | <ng-container *ngIf="!search" i18n>No server found.</ng-container> |
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.scss b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss index 2c46c3d03..376fb1693 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.scss +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss | |||
@@ -2,7 +2,6 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .description { | 4 | .description { |
5 | font-size: 15px; | ||
6 | margin-bottom: 15px; | 5 | margin-bottom: 15px; |
7 | } | 6 | } |
8 | 7 | ||
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts index 9edfac388..617408f2a 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { forkJoin } from 'rxjs' | 1 | import { forkJoin } from 'rxjs' |
2 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 2 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { prepareIcu } from '@app/helpers' | ||
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
@@ -63,9 +64,16 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
63 | forkJoin(observables) | 64 | forkJoin(observables) |
64 | .subscribe({ | 65 | .subscribe({ |
65 | next: () => { | 66 | next: () => { |
66 | const message = Array.isArray(this.usersToBan) | 67 | let message: string |
67 | ? $localize`${this.usersToBan.length} users banned.` | 68 | |
68 | : $localize`User ${this.usersToBan.username} banned.` | 69 | if (Array.isArray(this.usersToBan)) { |
70 | message = prepareIcu($localize`{count, plural, =1 {1 user banned.} other {{count} users banned.}}`)( | ||
71 | { count: this.usersToBan.length }, | ||
72 | $localize`${this.usersToBan.length} users banned.` | ||
73 | ) | ||
74 | } else { | ||
75 | message = $localize`User ${this.usersToBan.username} banned.` | ||
76 | } | ||
69 | 77 | ||
70 | this.notifier.success(message) | 78 | this.notifier.success(message) |
71 | 79 | ||
@@ -79,7 +87,12 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
79 | } | 87 | } |
80 | 88 | ||
81 | getModalTitle () { | 89 | getModalTitle () { |
82 | if (Array.isArray(this.usersToBan)) return $localize`Ban ${this.usersToBan.length} users` | 90 | if (Array.isArray(this.usersToBan)) { |
91 | return prepareIcu($localize`Ban {count, plural, =1 {1 user} other {{count} users}}`)( | ||
92 | { count: this.usersToBan.length }, | ||
93 | $localize`Ban ${this.usersToBan.length} users` | ||
94 | ) | ||
95 | } | ||
83 | 96 | ||
84 | return $localize`Ban "${this.usersToBan.username}"` | 97 | return $localize`Ban "${this.usersToBan.username}"` |
85 | } | 98 | } |
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 787318c2c..c69a45c25 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 | |||
@@ -100,7 +100,8 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
100 | return | 100 | return |
101 | } | 101 | } |
102 | 102 | ||
103 | const message = $localize`If you remove user ${user.username}, you won't be able to create another with the same username!` | 103 | // eslint-disable-next-line max-len |
104 | const message = $localize`If you remove this user, you won't be able to create another user or channel with <strong>${user.username}</strong> username!` | ||
104 | const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`) | 105 | const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`) |
105 | if (res === false) return | 106 | if (res === false) return |
106 | 107 | ||
diff --git a/client/src/app/shared/shared-moderation/video-block.component.scss b/client/src/app/shared/shared-moderation/video-block.component.scss index 3061bbf15..7726eca11 100644 --- a/client/src/app/shared/shared-moderation/video-block.component.scss +++ b/client/src/app/shared/shared-moderation/video-block.component.scss | |||
@@ -6,6 +6,5 @@ textarea { | |||
6 | } | 6 | } |
7 | 7 | ||
8 | .live-info { | 8 | .live-info { |
9 | font-size: 15px; | ||
10 | margin: 40px 0 20px; | 9 | margin: 40px 0 20px; |
11 | } | 10 | } |
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts index 400913f02..f8b22a3f6 100644 --- a/client/src/app/shared/shared-moderation/video-block.component.ts +++ b/client/src/app/shared/shared-moderation/video-block.component.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { prepareIcu } from '@app/helpers' | ||
3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { Video } from '@app/shared/shared-main' | 5 | import { Video } from '@app/shared/shared-main' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
@@ -80,9 +81,10 @@ export class VideoBlockComponent extends FormReactive implements OnInit { | |||
80 | this.videoBlocklistService.blockVideo(options) | 81 | this.videoBlocklistService.blockVideo(options) |
81 | .subscribe({ | 82 | .subscribe({ |
82 | next: () => { | 83 | next: () => { |
83 | const message = this.isMultiple | 84 | const message = prepareIcu($localize`{count, plural, =1 {Blocked {videoName}.} other {Blocked {count} videos.}}`)( |
84 | ? $localize`Blocked ${this.videos.length} videos.` | 85 | { count: this.videos.length, videoName: this.getSingleVideo().name }, |
85 | : $localize`Blocked ${this.getSingleVideo().name}` | 86 | $localize`Blocked ${this.videos.length} videos.` |
87 | ) | ||
86 | 88 | ||
87 | this.notifier.success(message) | 89 | this.notifier.success(message) |
88 | this.hide() | 90 | this.hide() |
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.html b/client/src/app/shared/shared-share-modal/video-share.component.html index 67ca56516..b163d3581 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.html +++ b/client/src/app/shared/shared-share-modal/video-share.component.html | |||
@@ -8,7 +8,7 @@ | |||
8 | <div class="modal-body"> | 8 | <div class="modal-body"> |
9 | 9 | ||
10 | <div class="playlist" *ngIf="playlist"> | 10 | <div class="playlist" *ngIf="playlist"> |
11 | <div class="title-page title-page-single" i18n *ngIf="video">Share the playlist</div> | 11 | <h5 i18n *ngIf="video">Share the playlist</h5> |
12 | 12 | ||
13 | <div *ngIf="isPrivatePlaylist()" class="alert-private alert alert-warning"> | 13 | <div *ngIf="isPrivatePlaylist()" class="alert-private alert alert-warning"> |
14 | <div i18n>This playlist is private so you won't be able to share it with external users</div> | 14 | <div i18n>This playlist is private so you won't be able to share it with external users</div> |
@@ -25,8 +25,7 @@ | |||
25 | 25 | ||
26 | <ng-template ngbNavContent> | 26 | <ng-template ngbNavContent> |
27 | <div class="nav-content"> | 27 | <div class="nav-content"> |
28 | 28 | <my-input-text [value]="getPlaylistUrl()" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> | |
29 | <my-input-toggle-hidden [value]="getPlaylistUrl()" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | ||
30 | </div> | 29 | </div> |
31 | </ng-template> | 30 | </ng-template> |
32 | </ng-container> | 31 | </ng-container> |
@@ -46,10 +45,10 @@ | |||
46 | 45 | ||
47 | <ng-template ngbNavContent> | 46 | <ng-template ngbNavContent> |
48 | <div class="nav-content"> | 47 | <div class="nav-content"> |
49 | <my-input-toggle-hidden | 48 | <my-input-text |
50 | [value]="customizations.onlyEmbedUrl ? getPlaylistEmbedUrl() : getPlaylistIframeCode()" (change)="updateEmbedCode()" | 49 | [value]="customizations.onlyEmbedUrl ? getPlaylistEmbedUrl() : getPlaylistIframeCode()" (change)="updateEmbedCode()" |
51 | [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true" | 50 | [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true" |
52 | ></my-input-toggle-hidden> | 51 | ></my-input-text> |
53 | 52 | ||
54 | <div i18n *ngIf="notSecure()" class="alert alert-warning"> | 53 | <div i18n *ngIf="notSecure()" class="alert alert-warning"> |
55 | The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). | 54 | The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). |
@@ -86,7 +85,7 @@ | |||
86 | 85 | ||
87 | 86 | ||
88 | <div class="video" *ngIf="video"> | 87 | <div class="video" *ngIf="video"> |
89 | <div class="title-page title-page-single" *ngIf="playlist" i18n>Share the video</div> | 88 | <h5 *ngIf="playlist" i18n>Share the video</h5> |
90 | 89 | ||
91 | <div *ngIf="isPrivateVideo()" class="alert-private alert alert-warning"> | 90 | <div *ngIf="isPrivateVideo()" class="alert-private alert alert-warning"> |
92 | <div i18n>This video is private so you won't be able to share it with external users</div> | 91 | <div i18n>This video is private so you won't be able to share it with external users</div> |
@@ -103,7 +102,7 @@ | |||
103 | 102 | ||
104 | <ng-template ngbNavContent> | 103 | <ng-template ngbNavContent> |
105 | <div class="nav-content"> | 104 | <div class="nav-content"> |
106 | <my-input-toggle-hidden [value]="getVideoUrl()" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | 105 | <my-input-text [value]="getVideoUrl()" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> |
107 | </div> | 106 | </div> |
108 | </ng-template> | 107 | </ng-template> |
109 | </ng-container> | 108 | </ng-container> |
@@ -123,10 +122,10 @@ | |||
123 | 122 | ||
124 | <ng-template ngbNavContent> | 123 | <ng-template ngbNavContent> |
125 | <div class="nav-content"> | 124 | <div class="nav-content"> |
126 | <my-input-toggle-hidden | 125 | <my-input-text |
127 | [value]="customizations.onlyEmbedUrl ? getVideoEmbedUrl() : getVideoIframeCode()" (ngModelChange)="updateEmbedCode()" | 126 | [value]="customizations.onlyEmbedUrl ? getVideoEmbedUrl() : getVideoIframeCode()" (ngModelChange)="updateEmbedCode()" |
128 | [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true" | 127 | [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true" |
129 | ></my-input-toggle-hidden> | 128 | ></my-input-text> |
130 | 129 | ||
131 | <div i18n *ngIf="notSecure()" class="alert alert-warning"> | 130 | <div i18n *ngIf="notSecure()" class="alert alert-warning"> |
132 | The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). | 131 | The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). |
@@ -171,9 +170,8 @@ | |||
171 | </div> | 170 | </div> |
172 | </div> | 171 | </div> |
173 | 172 | ||
174 | <div class="form-group"> | 173 | <div class="form-group" *ngIf="isInVideoEmbedTab()"> |
175 | <my-peertube-checkbox | 174 | <my-peertube-checkbox |
176 | *ngIf="isInVideoEmbedTab()" | ||
177 | inputName="onlyEmbedUrl" [(ngModel)]="customizations.onlyEmbedUrl" | 175 | inputName="onlyEmbedUrl" [(ngModel)]="customizations.onlyEmbedUrl" |
178 | i18n-labelText labelText="Only display embed URL" | 176 | i18n-labelText labelText="Only display embed URL" |
179 | ></my-peertube-checkbox> | 177 | ></my-peertube-checkbox> |
@@ -268,7 +266,7 @@ | |||
268 | [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic"> | 266 | [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic"> |
269 | 267 | ||
270 | <ng-container *ngIf="isAdvancedCustomizationCollapsed"> | 268 | <ng-container *ngIf="isAdvancedCustomizationCollapsed"> |
271 | <span class="glyphicon glyphicon-menu-down"></span> | 269 | <span class="chevron-down"></span> |
272 | 270 | ||
273 | <ng-container i18n> | 271 | <ng-container i18n> |
274 | More customization | 272 | More customization |
@@ -276,7 +274,7 @@ | |||
276 | </ng-container> | 274 | </ng-container> |
277 | 275 | ||
278 | <ng-container *ngIf="!isAdvancedCustomizationCollapsed"> | 276 | <ng-container *ngIf="!isAdvancedCustomizationCollapsed"> |
279 | <span class="glyphicon glyphicon-menu-up"></span> | 277 | <span class="chevron-up"></span> |
280 | 278 | ||
281 | <ng-container i18n> | 279 | <ng-container i18n> |
282 | Less customization | 280 | Less customization |
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.scss b/client/src/app/shared/shared-share-modal/video-share.component.scss index 44ebb13f4..6e80f8c76 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.scss +++ b/client/src/app/shared/shared-share-modal/video-share.component.scss | |||
@@ -1,14 +1,10 @@ | |||
1 | @use '_mixins' as *; | 1 | @use '_mixins' as *; |
2 | @use '_variables' as *; | 2 | @use '_variables' as *; |
3 | 3 | ||
4 | my-input-toggle-hidden { | 4 | my-input-text { |
5 | width: 100%; | 5 | width: 100%; |
6 | } | 6 | } |
7 | 7 | ||
8 | .title-page.title-page-single { | ||
9 | margin-top: 0; | ||
10 | } | ||
11 | |||
12 | .playlist { | 8 | .playlist { |
13 | margin-bottom: 50px; | 9 | margin-bottom: 50px; |
14 | } | 10 | } |
@@ -34,6 +30,10 @@ my-input-toggle-hidden { | |||
34 | margin-top: 20px; | 30 | margin-top: 20px; |
35 | } | 31 | } |
36 | 32 | ||
33 | .alert-private { | ||
34 | margin-top: 0; | ||
35 | } | ||
36 | |||
37 | .filters { | 37 | .filters { |
38 | margin-top: 30px; | 38 | margin-top: 30px; |
39 | 39 | ||
@@ -46,20 +46,8 @@ my-input-toggle-hidden { | |||
46 | justify-content: center; | 46 | justify-content: center; |
47 | align-items: center; | 47 | align-items: center; |
48 | margin-top: 20px; | 48 | margin-top: 20px; |
49 | font-size: 16px; | ||
50 | font-weight: $font-semibold; | 49 | font-weight: $font-semibold; |
51 | cursor: pointer; | 50 | cursor: pointer; |
52 | |||
53 | .glyphicon { | ||
54 | @include margin-right(5px); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | .form-group { | ||
59 | margin-bottom: 0; | ||
60 | height: 34px; | ||
61 | display: flex; | ||
62 | align-items: center; | ||
63 | } | 51 | } |
64 | 52 | ||
65 | .video-caption-block { | 53 | .video-caption-block { |
@@ -88,3 +76,7 @@ my-input-toggle-hidden { | |||
88 | align-items: center; | 76 | align-items: center; |
89 | justify-content: space-between; | 77 | justify-content: space-between; |
90 | } | 78 | } |
79 | |||
80 | h5 { | ||
81 | font-size: 1.15rem; | ||
82 | } | ||
diff --git a/client/src/app/shared/shared-tables/table-expander-icon.component.ts b/client/src/app/shared/shared-tables/table-expander-icon.component.ts index 3756b475a..66bbfe6fb 100644 --- a/client/src/app/shared/shared-tables/table-expander-icon.component.ts +++ b/client/src/app/shared/shared-tables/table-expander-icon.component.ts | |||
@@ -4,7 +4,7 @@ import { Component, Input } from '@angular/core' | |||
4 | selector: 'my-table-expander-icon', | 4 | selector: 'my-table-expander-icon', |
5 | template: ` | 5 | template: ` |
6 | <span class="expander"> | 6 | <span class="expander"> |
7 | <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i> | 7 | <i [ngClass]="expanded ? 'chevron-down' : 'chevron-right'"></i> |
8 | </span>` | 8 | </span>` |
9 | }) | 9 | }) |
10 | export class TableExpanderIconComponent { | 10 | export class TableExpanderIconComponent { |
diff --git a/client/src/app/shared/shared-tables/video-cell.component.scss b/client/src/app/shared/shared-tables/video-cell.component.scss index 7efb61502..5d26b02ef 100644 --- a/client/src/app/shared/shared-tables/video-cell.component.scss +++ b/client/src/app/shared/shared-tables/video-cell.component.scss | |||
@@ -59,13 +59,6 @@ | |||
59 | color: pvar(--mainForegroundColor); | 59 | color: pvar(--mainForegroundColor); |
60 | line-height: 1rem; | 60 | line-height: 1rem; |
61 | 61 | ||
62 | div .glyphicon { | ||
63 | @include margin-left(0.1rem); | ||
64 | |||
65 | font-size: 80%; | ||
66 | color: #808080; | ||
67 | } | ||
68 | |||
69 | div + div { | 62 | div + div { |
70 | color: var(--greyForegroundColor); | 63 | color: var(--greyForegroundColor); |
71 | font-size: 11px; | 64 | font-size: 11px; |
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.html b/client/src/app/shared/shared-user-settings/user-interface-settings.component.html index b739e881b..1e6e55e98 100644 --- a/client/src/app/shared/shared-user-settings/user-interface-settings.component.html +++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.html | |||
@@ -5,10 +5,10 @@ | |||
5 | 5 | ||
6 | <div class="peertube-select-container"> | 6 | <div class="peertube-select-container"> |
7 | <select formControlName="theme" id="theme" class="form-control"> | 7 | <select formControlName="theme" id="theme" class="form-control"> |
8 | <option i18n value="instance-default">Instance default theme ({{ getDefaultThemeLabel() }})</option> | 8 | <option i18n value="instance-default">{{ instanceName }} default theme ({{ getDefaultInstanceThemeLabel() }})</option> |
9 | <option i18n value="default">{{ defaultThemeLabel }}</option> | 9 | <option i18n value="default">{{ getDefaultThemeLabel() }}</option> |
10 | 10 | ||
11 | <option *ngFor="let theme of availableThemes" [value]="theme">{{ capitalizeFirstLetter(theme) }}</option> | 11 | <option *ngFor="let theme of availableThemes" [value]="theme.id">{{ theme.label }}</option> |
12 | </select> | 12 | </select> |
13 | </div> | 13 | </div> |
14 | </div> | 14 | </div> |
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss b/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss index 2fc245ace..da8202594 100644 --- a/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss +++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss | |||
@@ -1,11 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | input[type=submit] { | 4 | input[type=submit] { |
10 | @include peertube-button; | 5 | @include peertube-button; |
11 | @include orange-button; | 6 | @include orange-button; |
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts index 932db498a..13e2e5424 100644 --- a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts +++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { Subject, Subscription } from 'rxjs' | 1 | import { Subject, Subscription } from 'rxjs' |
2 | import { Component, Input, OnDestroy, OnInit } from '@angular/core' | 2 | import { Component, Input, OnDestroy, OnInit } from '@angular/core' |
3 | import { AuthService, Notifier, ServerService, UserService } from '@app/core' | 3 | import { AuthService, Notifier, ServerService, ThemeService, UserService } from '@app/core' |
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
5 | import { capitalizeFirstLetter } from '@root-helpers/string' | ||
6 | import { HTMLServerConfig, User, UserUpdateMe } from '@shared/models' | 5 | import { HTMLServerConfig, User, UserUpdateMe } from '@shared/models' |
6 | import { SelectOptionsItem } from 'src/types' | ||
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-user-interface-settings', | 9 | selector: 'my-user-interface-settings', |
@@ -16,10 +16,9 @@ export class UserInterfaceSettingsComponent extends FormReactive implements OnIn | |||
16 | @Input() notifyOnUpdate = true | 16 | @Input() notifyOnUpdate = true |
17 | @Input() userInformationLoaded: Subject<any> | 17 | @Input() userInformationLoaded: Subject<any> |
18 | 18 | ||
19 | availableThemes: SelectOptionsItem[] | ||
19 | formValuesWatcher: Subscription | 20 | formValuesWatcher: Subscription |
20 | 21 | ||
21 | defaultThemeLabel = $localize`Light/Orange` | ||
22 | |||
23 | private serverConfig: HTMLServerConfig | 22 | private serverConfig: HTMLServerConfig |
24 | 23 | ||
25 | constructor ( | 24 | constructor ( |
@@ -27,19 +26,21 @@ export class UserInterfaceSettingsComponent extends FormReactive implements OnIn | |||
27 | private authService: AuthService, | 26 | private authService: AuthService, |
28 | private notifier: Notifier, | 27 | private notifier: Notifier, |
29 | private userService: UserService, | 28 | private userService: UserService, |
29 | private themeService: ThemeService, | ||
30 | private serverService: ServerService | 30 | private serverService: ServerService |
31 | ) { | 31 | ) { |
32 | super() | 32 | super() |
33 | } | 33 | } |
34 | 34 | ||
35 | get availableThemes () { | 35 | get instanceName () { |
36 | return this.serverConfig.theme.registered | 36 | return this.serverConfig.instance.name |
37 | .map(t => t.name) | ||
38 | } | 37 | } |
39 | 38 | ||
40 | ngOnInit () { | 39 | ngOnInit () { |
41 | this.serverConfig = this.serverService.getHTMLConfig() | 40 | this.serverConfig = this.serverService.getHTMLConfig() |
42 | 41 | ||
42 | this.availableThemes = this.themeService.buildAvailableThemes() | ||
43 | |||
43 | this.buildForm({ | 44 | this.buildForm({ |
44 | theme: null | 45 | theme: null |
45 | }) | 46 | }) |
@@ -61,17 +62,19 @@ export class UserInterfaceSettingsComponent extends FormReactive implements OnIn | |||
61 | } | 62 | } |
62 | 63 | ||
63 | getDefaultThemeLabel () { | 64 | getDefaultThemeLabel () { |
65 | return this.themeService.getDefaultThemeLabel() | ||
66 | } | ||
67 | |||
68 | getDefaultInstanceThemeLabel () { | ||
64 | const theme = this.serverConfig.theme.default | 69 | const theme = this.serverConfig.theme.default |
65 | 70 | ||
66 | if (theme === 'default') return this.defaultThemeLabel | 71 | if (theme === 'default') { |
72 | return this.getDefaultThemeLabel() | ||
73 | } | ||
67 | 74 | ||
68 | return theme | 75 | return theme |
69 | } | 76 | } |
70 | 77 | ||
71 | capitalizeFirstLetter (str: string) { | ||
72 | return capitalizeFirstLetter(str) | ||
73 | } | ||
74 | |||
75 | updateInterfaceSettings () { | 78 | updateInterfaceSettings () { |
76 | const theme = this.form.value['theme'] | 79 | const theme = this.form.value['theme'] |
77 | 80 | ||
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.html b/client/src/app/shared/shared-user-settings/user-video-settings.component.html index 446ade445..85b27a4ff 100644 --- a/client/src/app/shared/shared-user-settings/user-video-settings.component.html +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | 1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> |
2 | <div class="form-group form-group-select"> | 2 | <div class="form-group"> |
3 | <div class="anchor" id="video-sensitive-content-policy"></div> <!-- video-sensitive-content-policy anchor --> | 3 | <div class="anchor" id="video-sensitive-content-policy"></div> <!-- video-sensitive-content-policy anchor --> |
4 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> | 4 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> |
5 | <my-help> | 5 | <my-help> |
@@ -20,7 +20,7 @@ | |||
20 | </div> | 20 | </div> |
21 | </div> | 21 | </div> |
22 | 22 | ||
23 | <div class="form-group form-group-select"> | 23 | <div class="form-group"> |
24 | <div class="anchor" id="video-languages-subtitles"></div> <!-- video-languages-subtitles anchor --> | 24 | <div class="anchor" id="video-languages-subtitles"></div> <!-- video-languages-subtitles anchor --> |
25 | <label i18n for="videoLanguages">Only display videos in the following languages/subtitles</label> | 25 | <label i18n for="videoLanguages">Only display videos in the following languages/subtitles</label> |
26 | <my-help> | 26 | <my-help> |
@@ -30,7 +30,7 @@ | |||
30 | </my-help> | 30 | </my-help> |
31 | 31 | ||
32 | <div> | 32 | <div> |
33 | <my-select-languages formControlName="videoLanguages"></my-select-languages> | 33 | <my-select-languages [maxLanguages]="20" formControlName="videoLanguages"></my-select-languages> |
34 | </div> | 34 | </div> |
35 | </div> | 35 | </div> |
36 | 36 | ||
@@ -42,7 +42,7 @@ | |||
42 | i18n-labelText labelText="Help share videos being played" | 42 | i18n-labelText labelText="Help share videos being played" |
43 | > | 43 | > |
44 | <ng-container ngProjectAs="description"> | 44 | <ng-container ngProjectAs="description"> |
45 | <span i18n>The <a routerLink="/about/peertube" fragment="privacy" target="_blank">sharing system</a> implies that some technical information about your system (such as a public IP address) can be sent to other peers, but greatly helps to reduce server load.</span> | 45 | <span i18n>The <a class="link-orange" routerLink="/about/peertube" fragment="privacy" target="_blank">sharing system</a> implies that some technical information about your system (such as a public IP address) can be sent to other peers, but greatly helps to reduce server load.</span> |
46 | </ng-container> | 46 | </ng-container> |
47 | </my-peertube-checkbox> | 47 | </my-peertube-checkbox> |
48 | </div> | 48 | </div> |
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.scss b/client/src/app/shared/shared-user-settings/user-video-settings.component.scss index c4f6020d4..163c899d3 100644 --- a/client/src/app/shared/shared-user-settings/user-video-settings.component.scss +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.scss | |||
@@ -1,11 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | input[type=submit] { | 4 | input[type=submit] { |
10 | @include peertube-button; | 5 | @include peertube-button; |
11 | @include orange-button; | 6 | @include orange-button; |
@@ -15,8 +10,6 @@ input[type=submit] { | |||
15 | 10 | ||
16 | .peertube-select-container { | 11 | .peertube-select-container { |
17 | @include peertube-select-container(340px); | 12 | @include peertube-select-container(340px); |
18 | |||
19 | margin-bottom: 30px; | ||
20 | } | 13 | } |
21 | 14 | ||
22 | my-select-languages { | 15 | my-select-languages { |
@@ -24,7 +17,3 @@ my-select-languages { | |||
24 | 17 | ||
25 | display: block; | 18 | display: block; |
26 | } | 19 | } |
27 | |||
28 | .form-group-select { | ||
29 | margin-bottom: 30px; | ||
30 | } | ||
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html index a00c3d1c7..656d1beb3 100644 --- a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html +++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <form novalidate [formGroup]="form" (ngSubmit)="formValidated()"> | 1 | <form novalidate [formGroup]="form" (ngSubmit)="formValidated()"> |
2 | <div class="form-group mb-2"> | 2 | <div class="form-group"> |
3 | <input type="email" | 3 | <input type="email" |
4 | formControlName="text" | 4 | formControlName="text" |
5 | class="form-control" | 5 | class="form-control" |
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.html b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html index a6d851315..0e09c2697 100644 --- a/client/src/app/shared/shared-user-subscription/subscribe-button.component.html +++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div | 1 | <div |
2 | class="btn-group-subscribe btn-group" | 2 | class="btn-group-subscribe btn-group" role="group" |
3 | [ngClass]="{'subscribe-button': !isAllChannelsSubscribed, 'unsubscribe-button': isAllChannelsSubscribed, 'big': isBigButton }" | 3 | [ngClass]="{'subscribe-button': !isAllChannelsSubscribed, 'unsubscribe-button': isAllChannelsSubscribed, 'big': isBigButton }" |
4 | > | 4 | > |
5 | 5 | ||
@@ -20,17 +20,11 @@ | |||
20 | </ng-template> | 20 | </ng-template> |
21 | 21 | ||
22 | <ng-template #userLoggedIn> | 22 | <ng-template #userLoggedIn> |
23 | <button | 23 | <button *ngIf="!isAllChannelsSubscribed" type="button" class="btn" (click)="subscribe()"> |
24 | *ngIf="!isAllChannelsSubscribed" type="button" | ||
25 | class="btn btn-sm" (click)="subscribe()" | ||
26 | > | ||
27 | <ng-template [ngTemplateOutlet]="userLoggedOut"></ng-template> | 24 | <ng-template [ngTemplateOutlet]="userLoggedOut"></ng-template> |
28 | </button> | 25 | </button> |
29 | 26 | ||
30 | <button | 27 | <button *ngIf="isAllChannelsSubscribed" type="button" class="btn" role="button" (click)="unsubscribe()"> |
31 | *ngIf="isAllChannelsSubscribed" type="button" | ||
32 | class="btn btn-sm" role="button" | ||
33 | (click)="unsubscribe()"> | ||
34 | <ng-container i18n>{account + "", select, undefined {Unsubscribe} other {Unsubscribe from all channels}}</ng-container> | 28 | <ng-container i18n>{account + "", select, undefined {Unsubscribe} other {Unsubscribe from all channels}}</ng-container> |
35 | </button> | 29 | </button> |
36 | </ng-template> | 30 | </ng-template> |
@@ -43,7 +37,7 @@ | |||
43 | class="btn-group" ngbDropdown autoClose="outside" placement="bottom-right bottom-left bottom auto" | 37 | class="btn-group" ngbDropdown autoClose="outside" placement="bottom-right bottom-left bottom auto" |
44 | role="group" aria-label="Multiple ways to subscribe to the current channel" i18n-aria-label | 38 | role="group" aria-label="Multiple ways to subscribe to the current channel" i18n-aria-label |
45 | > | 39 | > |
46 | <button class="btn btn-sm dropdown-toggle-split" ngbDropdownToggle aria-label="Open subscription dropdown" i18n-aria-label> | 40 | <button class="btn dropdown-toggle-split" ngbDropdownToggle aria-label="Open subscription dropdown" i18n-aria-label> |
47 | <ng-container | 41 | <ng-container |
48 | *ngIf="!isUserLoggedIn(); then userLoggedOut"> | 42 | *ngIf="!isUserLoggedIn(); then userLoggedOut"> |
49 | </ng-container> | 43 | </ng-container> |
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss index da8eaf646..889596b62 100644 --- a/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss +++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss | |||
@@ -3,14 +3,10 @@ | |||
3 | 3 | ||
4 | .btn-group-subscribe { | 4 | .btn-group-subscribe { |
5 | @include peertube-button; | 5 | @include peertube-button; |
6 | @include disable-default-a-behaviour; | ||
7 | 6 | ||
8 | float: right; | 7 | button.dropdown-toggle { |
9 | padding: 0; | 8 | font-size: $button-font-size; |
10 | 9 | line-height: 1.2; | |
11 | > .btn, | ||
12 | > .dropdown > .dropdown-toggle { | ||
13 | font-size: 15px; | ||
14 | } | 10 | } |
15 | 11 | ||
16 | &:not(.big) { | 12 | &:not(.big) { |
@@ -38,7 +34,7 @@ | |||
38 | 34 | ||
39 | // Unlogged | 35 | // Unlogged |
40 | > .dropdown > .dropdown-toggle span { | 36 | > .dropdown > .dropdown-toggle span { |
41 | @include padding-right(3px); | 37 | @include padding-right(5px); |
42 | } | 38 | } |
43 | 39 | ||
44 | // Logged | 40 | // Logged |
@@ -65,9 +61,11 @@ | |||
65 | @include padding-left(5px); | 61 | @include padding-left(5px); |
66 | } | 62 | } |
67 | } | 63 | } |
64 | |||
68 | &.unsubscribe-button { | 65 | &.unsubscribe-button { |
69 | .btn { | 66 | .btn { |
70 | @include grey-button; | 67 | @include grey-button; |
68 | |||
71 | font-weight: 600; | 69 | font-weight: 600; |
72 | } | 70 | } |
73 | } | 71 | } |
diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts index 6f2ef50cb..8cd94643a 100644 --- a/client/src/app/shared/shared-video-comment/video-comment.service.ts +++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts | |||
@@ -31,7 +31,7 @@ export class VideoCommentService { | |||
31 | private restService: RestService | 31 | private restService: RestService |
32 | ) {} | 32 | ) {} |
33 | 33 | ||
34 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { | 34 | addCommentThread (videoId: string, comment: VideoCommentCreate) { |
35 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | 35 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' |
36 | const normalizedComment = objectLineFeedToHtml(comment, 'text') | 36 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
37 | 37 | ||
@@ -42,7 +42,7 @@ export class VideoCommentService { | |||
42 | ) | 42 | ) |
43 | } | 43 | } |
44 | 44 | ||
45 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { | 45 | addCommentReply (videoId: string, inReplyToCommentId: number, comment: VideoCommentCreate) { |
46 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId | 46 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId |
47 | const normalizedComment = objectLineFeedToHtml(comment, 'text') | 47 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
48 | 48 | ||
@@ -75,7 +75,7 @@ export class VideoCommentService { | |||
75 | } | 75 | } |
76 | 76 | ||
77 | getVideoCommentThreads (parameters: { | 77 | getVideoCommentThreads (parameters: { |
78 | videoId: number | string | 78 | videoId: string |
79 | componentPagination: ComponentPaginationLight | 79 | componentPagination: ComponentPaginationLight |
80 | sort: string | 80 | sort: string |
81 | }): Observable<ThreadsResultList<VideoComment>> { | 81 | }): Observable<ThreadsResultList<VideoComment>> { |
@@ -95,7 +95,7 @@ export class VideoCommentService { | |||
95 | } | 95 | } |
96 | 96 | ||
97 | getVideoThreadComments (parameters: { | 97 | getVideoThreadComments (parameters: { |
98 | videoId: number | string | 98 | videoId: string |
99 | threadId: number | 99 | threadId: number |
100 | }): Observable<VideoCommentThreadTree> { | 100 | }): Observable<VideoCommentThreadTree> { |
101 | const { videoId, threadId } = parameters | 101 | const { videoId, threadId } = parameters |
diff --git a/client/src/app/shared/shared-video-live/live-documentation-link.component.html b/client/src/app/shared/shared-video-live/live-documentation-link.component.html index acf8a71eb..27248645f 100644 --- a/client/src/app/shared/shared-video-live/live-documentation-link.component.html +++ b/client/src/app/shared/shared-video-live/live-documentation-link.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div i18n> | 1 | <p i18n> |
2 | See <a href="https://docs.joinpeertube.org/use-create-upload-video?id=publish-a-live-in-peertube-gt-v3" target="_blank" rel="noopener noreferrer">the documentation</a> | 2 | See <a class="link-orange" href="https://docs.joinpeertube.org/use-create-upload-video?id=publish-a-live-in-peertube-gt-v3" target="_blank" rel="noopener noreferrer">the documentation</a> |
3 | to learn how to use the PeerTube live streaming feature. | 3 | to learn how to use the PeerTube live streaming feature. |
4 | </div> | 4 | </p> |
diff --git a/client/src/app/shared/shared-video-live/live-stream-information.component.html b/client/src/app/shared/shared-video-live/live-stream-information.component.html index 01e305938..99c7dbd4c 100644 --- a/client/src/app/shared/shared-video-live/live-stream-information.component.html +++ b/client/src/app/shared/shared-video-live/live-stream-information.component.html | |||
@@ -7,27 +7,27 @@ | |||
7 | 7 | ||
8 | <div class="modal-body" *ngIf="live"> | 8 | <div class="modal-body" *ngIf="live"> |
9 | <div> | 9 | <div> |
10 | <div class="badge badge-info" *ngIf="live.permanentLive" i18n>Permanent/Recurring live</div> | 10 | <div class="pt-badge badge-blue" *ngIf="live.permanentLive" i18n>Permanent/Recurring live</div> |
11 | <div class="badge badge-info" *ngIf="live.saveReplay" i18n>Replay will be saved</div> | 11 | <div class="pt-badge badge-blue" *ngIf="live.saveReplay" i18n>Replay will be saved</div> |
12 | </div> | 12 | </div> |
13 | 13 | ||
14 | <div class="alert alert-info"> | 14 | <div class="alert pt-alert-primary"> |
15 | <my-live-documentation-link></my-live-documentation-link> | 15 | <my-live-documentation-link></my-live-documentation-link> |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <div *ngIf="live.rtmpUrl" class="form-group"> | 18 | <div *ngIf="live.rtmpUrl" class="form-group"> |
19 | <label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label> | 19 | <label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label> |
20 | <my-input-toggle-hidden inputId="liveVideoRTMPUrl" [value]="live.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | 20 | <my-input-text inputId="liveVideoRTMPUrl" [value]="live.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> |
21 | </div> | 21 | </div> |
22 | 22 | ||
23 | <div *ngIf="live.rtmpsUrl" class="form-group"> | 23 | <div *ngIf="live.rtmpsUrl" class="form-group"> |
24 | <label for="liveVideoRTMPSUrl" i18n>Live RTMPS Url</label> | 24 | <label for="liveVideoRTMPSUrl" i18n>Live RTMPS Url</label> |
25 | <my-input-toggle-hidden inputId="liveVideoRTMPSUrl" [value]="live.rtmpsUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden> | 25 | <my-input-text inputId="liveVideoRTMPSUrl" [value]="live.rtmpsUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-text> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <div class="form-group"> | 28 | <div class="form-group"> |
29 | <label for="liveVideoStreamKey" i18n>Live stream key</label> | 29 | <label for="liveVideoStreamKey" i18n>Live stream key</label> |
30 | <my-input-toggle-hidden inputId="liveVideoStreamKey" [value]="live.streamKey" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden> | 30 | <my-input-text inputId="liveVideoStreamKey" [value]="live.streamKey" [withCopy]="true" [readonly]="true"></my-input-text> |
31 | 31 | ||
32 | <div class="form-group-description" i18n>⚠️ Never share your stream key with anyone.</div> | 32 | <div class="form-group-description" i18n>⚠️ Never share your stream key with anyone.</div> |
33 | </div> | 33 | </div> |
@@ -36,8 +36,8 @@ | |||
36 | <label i18n>Latest live sessions</label> | 36 | <label i18n>Latest live sessions</label> |
37 | 37 | ||
38 | <div class="journal-session" *ngFor="let session of latestLiveSessions"> | 38 | <div class="journal-session" *ngFor="let session of latestLiveSessions"> |
39 | <span i18n class="badge badge-success" *ngIf="!getErrorLabel(session)">Success</span> | 39 | <span i18n class="pt-badge badge-success" *ngIf="!getErrorLabel(session)">Success</span> |
40 | <span class="badge badge-danger" *ngIf="getErrorLabel(session)">{{ getErrorLabel(session) }}</span> | 40 | <span class="pt-badge badge-danger" *ngIf="getErrorLabel(session)">{{ getErrorLabel(session) }}</span> |
41 | 41 | ||
42 | <span i18n>Started on {{ session.startDate | date:'medium' }}</span> | 42 | <span i18n>Started on {{ session.startDate | date:'medium' }}</span> |
43 | <span i18n *ngIf="session.endDate">Ended on {{ session.endDate | date:'medium' }}</span> | 43 | <span i18n *ngIf="session.endDate">Ended on {{ session.endDate | date:'medium' }}</span> |
diff --git a/client/src/app/shared/shared-video-live/live-stream-information.component.scss b/client/src/app/shared/shared-video-live/live-stream-information.component.scss index 9c8ad12bd..a0cb30897 100644 --- a/client/src/app/shared/shared-video-live/live-stream-information.component.scss +++ b/client/src/app/shared/shared-video-live/live-stream-information.component.scss | |||
@@ -9,19 +9,20 @@ p-autocomplete { | |||
9 | margin: 20px 0; | 9 | margin: 20px 0; |
10 | } | 10 | } |
11 | 11 | ||
12 | .alert-info { | 12 | .pt-alert-primary { |
13 | margin: 1rem 0; | 13 | margin: 1rem 0; |
14 | } | 14 | } |
15 | 15 | ||
16 | .badge { | 16 | .pt-badge { |
17 | @include margin-right(5px); | ||
18 | |||
17 | font-size: 13px; | 19 | font-size: 13px; |
18 | margin-right: 5px; | ||
19 | } | 20 | } |
20 | 21 | ||
21 | .journal-session { | 22 | .journal-session { |
22 | margin-bottom: 5px; | 23 | margin-bottom: 5px; |
23 | 24 | ||
24 | span:not(.badge, :last-child)::after { | 25 | span:not(.pt-badge, :last-child)::after { |
25 | margin: 3px; | 26 | margin: 3px; |
26 | content: '•'; | 27 | content: '•'; |
27 | } | 28 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.html b/client/src/app/shared/shared-video-miniature/video-download.component.html index b50544057..1c7458b4b 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.html +++ b/client/src/app/shared/shared-video-miniature/video-download.component.html | |||
@@ -28,15 +28,10 @@ | |||
28 | 28 | ||
29 | <ng-template ngbNavContent> | 29 | <ng-template ngbNavContent> |
30 | <div class="nav-content"> | 30 | <div class="nav-content"> |
31 | <div class="input-group input-group-sm"> | 31 | <my-input-text |
32 | <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getLink()" /> | 32 | *ngIf="!isConfidentialVideo()" |
33 | 33 | [show]="true" [readonly]="true" [withCopy]="true" [withToggle]="false" [value]="getLink()" | |
34 | <div class="input-group-append" *ngIf="!isConfidentialVideo()"> | 34 | ></my-input-text> |
35 | <button [cdkCopyToClipboard]="urlInput.value" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary"> | ||
36 | <span class="glyphicon glyphicon-duplicate"></span> | ||
37 | </button> | ||
38 | </div> | ||
39 | </div> | ||
40 | </div> | 35 | </div> |
41 | </ng-template> | 36 | </ng-template> |
42 | </ng-container> | 37 | </ng-container> |
@@ -53,14 +48,10 @@ | |||
53 | 48 | ||
54 | <ng-template ngbNavContent> | 49 | <ng-template ngbNavContent> |
55 | <div class="nav-content"> | 50 | <div class="nav-content"> |
56 | <div class="input-group input-group-sm"> | 51 | <my-input-text |
57 | <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getLink()" /> | 52 | *ngIf="!isConfidentialVideo()" |
58 | <div class="input-group-append" *ngIf="!isConfidentialVideo()"> | 53 | [show]="true" [readonly]="true" [withCopy]="true" [withToggle]="false" [value]="getLink()" |
59 | <button [cdkCopyToClipboard]="urlInput.value" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary"> | 54 | ></my-input-text> |
60 | <span class="glyphicon glyphicon-duplicate"></span> | ||
61 | </button> | ||
62 | </div> | ||
63 | </div> | ||
64 | </div> | 55 | </div> |
65 | </ng-template> | 56 | </ng-template> |
66 | </ng-container> | 57 | </ng-container> |
@@ -129,7 +120,7 @@ | |||
129 | [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic" | 120 | [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic" |
130 | > | 121 | > |
131 | <ng-container *ngIf="isAdvancedCustomizationCollapsed"> | 122 | <ng-container *ngIf="isAdvancedCustomizationCollapsed"> |
132 | <span class="glyphicon glyphicon-menu-down"></span> | 123 | <span class="chevron-down"></span> |
133 | 124 | ||
134 | <ng-container i18n> | 125 | <ng-container i18n> |
135 | Advanced | 126 | Advanced |
@@ -137,7 +128,7 @@ | |||
137 | </ng-container> | 128 | </ng-container> |
138 | 129 | ||
139 | <ng-container *ngIf="!isAdvancedCustomizationCollapsed"> | 130 | <ng-container *ngIf="!isAdvancedCustomizationCollapsed"> |
140 | <span class="glyphicon glyphicon-menu-up"></span> | 131 | <span class="chevron-up"></span> |
141 | 132 | ||
142 | <ng-container i18n> | 133 | <ng-container i18n> |
143 | Simple | 134 | Simple |
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.scss b/client/src/app/shared/shared-video-miniature/video-download.component.scss index bd42f4813..407bdadf2 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-download.component.scss | |||
@@ -10,17 +10,12 @@ | |||
10 | justify-content: center; | 10 | justify-content: center; |
11 | align-items: center; | 11 | align-items: center; |
12 | margin-top: 20px; | 12 | margin-top: 20px; |
13 | font-size: 16px; | ||
14 | font-weight: 600; | 13 | font-weight: 600; |
15 | cursor: pointer; | 14 | cursor: pointer; |
16 | 15 | ||
17 | .nav-tabs { | 16 | .nav-tabs { |
18 | margin-top: 10x; | 17 | margin-top: 10x; |
19 | } | 18 | } |
20 | |||
21 | .glyphicon { | ||
22 | @include margin-right(5px); | ||
23 | } | ||
24 | } | 19 | } |
25 | 20 | ||
26 | .peertube-select-container.title-select { | 21 | .peertube-select-container.title-select { |
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts index 5328f5170..bbda39c2d 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts | |||
@@ -193,10 +193,6 @@ export class VideoDownloadComponent { | |||
193 | return this.video.privacy.id === VideoPrivacy.PRIVATE || this.video.privacy.id === VideoPrivacy.INTERNAL | 193 | return this.video.privacy.id === VideoPrivacy.PRIVATE || this.video.privacy.id === VideoPrivacy.INTERNAL |
194 | } | 194 | } |
195 | 195 | ||
196 | activateCopiedMessage () { | ||
197 | this.notifier.success($localize`Copied`) | ||
198 | } | ||
199 | |||
200 | switchToType (type: DownloadType) { | 196 | switchToType (type: DownloadType) { |
201 | this.type = type | 197 | this.type = type |
202 | } | 198 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html index a07b8b5ee..fe7a59bdb 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html | |||
@@ -44,6 +44,7 @@ | |||
44 | [searchable]="false" | 44 | [searchable]="false" |
45 | > | 45 | > |
46 | <ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option> | 46 | <ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option> |
47 | <ng-option i18n value="-originallyPublishedAt">Sort by <strong>"Original Publication Date"</strong></ng-option> | ||
47 | 48 | ||
48 | <ng-option i18n *ngIf="isTrendingSortEnabled('most-viewed')" value="-trending">Sort by <strong>"Recent Views"</strong></ng-option> | 49 | <ng-option i18n *ngIf="isTrendingSortEnabled('most-viewed')" value="-trending">Sort by <strong>"Recent Views"</strong></ng-option> |
49 | <ng-option i18n *ngIf="isTrendingSortEnabled('hot')" value="-hot">Sort by <strong>"Hot"</strong></ng-option> | 50 | <ng-option i18n *ngIf="isTrendingSortEnabled('hot')" value="-hot">Sort by <strong>"Hot"</strong></ng-option> |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss b/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss index 8cb1ff5b8..a4e51982c 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | .root { | 4 | .root { |
5 | margin-bottom: 45px; | 5 | margin-bottom: 45px; |
6 | font-size: 15px; | ||
7 | } | 6 | } |
8 | 7 | ||
9 | .first-row { | 8 | .first-row { |
@@ -49,7 +48,6 @@ | |||
49 | 48 | ||
50 | border-radius: 24px; | 49 | border-radius: 24px; |
51 | padding: 4px 15px; | 50 | padding: 4px 15px; |
52 | font-size: 16px; | ||
53 | margin-bottom: 15px; | 51 | margin-bottom: 15px; |
54 | cursor: pointer; | 52 | cursor: pointer; |
55 | } | 53 | } |
@@ -101,7 +99,7 @@ | |||
101 | } | 99 | } |
102 | 100 | ||
103 | .sort { | 101 | .sort { |
104 | min-width: 200px; | 102 | min-width: 250px; |
105 | max-width: 300px; | 103 | max-width: 300px; |
106 | height: min-content; | 104 | height: min-content; |
107 | 105 | ||
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html index 3cf128de0..e8d2ca1c4 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.html +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html | |||
@@ -12,13 +12,13 @@ | |||
12 | <div class="d-flex video-miniature-meta"> | 12 | <div class="d-flex video-miniature-meta"> |
13 | <my-actor-avatar | 13 | <my-actor-avatar |
14 | *ngIf="displayOptions.avatar && displayOwnerVideoChannel() && !displayAsRow" [title]="channelLinkTitle" | 14 | *ngIf="displayOptions.avatar && displayOwnerVideoChannel() && !displayAsRow" [title]="channelLinkTitle" |
15 | [channel]="video.channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]" | 15 | [actor]="video.channel" actorType="channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]" |
16 | size="32" | 16 | size="32" |
17 | ></my-actor-avatar> | 17 | ></my-actor-avatar> |
18 | 18 | ||
19 | <my-actor-avatar | 19 | <my-actor-avatar |
20 | *ngIf="displayOptions.avatar && displayOwnerAccount() && !displayAsRow" [title]="channelLinkTitle" | 20 | *ngIf="displayOptions.avatar && displayOwnerAccount() && !displayAsRow" [title]="channelLinkTitle" |
21 | [account]="video.account" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]" | 21 | [actor]="video.account" actorType="channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]" |
22 | size="32" | 22 | size="32" |
23 | ></my-actor-avatar> | 23 | ></my-actor-avatar> |
24 | 24 | ||
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.scss b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss index 80b418c87..a397efdca 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss | |||
@@ -4,6 +4,10 @@ | |||
4 | 4 | ||
5 | $more-button-width: 40px; | 5 | $more-button-width: 40px; |
6 | 6 | ||
7 | .video-miniature { | ||
8 | font-size: 14px; | ||
9 | } | ||
10 | |||
7 | .video-miniature-name { | 11 | .video-miniature-name { |
8 | @include miniature-name; | 12 | @include miniature-name; |
9 | } | 13 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index 42c472579..534a78b3f 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts | |||
@@ -175,7 +175,7 @@ export class VideoMiniatureComponent implements OnInit { | |||
175 | 175 | ||
176 | if (video.scheduledUpdate) { | 176 | if (video.scheduledUpdate) { |
177 | const updateAt = new Date(video.scheduledUpdate.updateAt.toString()).toLocaleString(this.localeId) | 177 | const updateAt = new Date(video.scheduledUpdate.updateAt.toString()).toLocaleString(this.localeId) |
178 | return $localize`Publication scheduled on ` + updateAt | 178 | return $localize`Publication scheduled on ${updateAt}` |
179 | } | 179 | } |
180 | 180 | ||
181 | if (video.state.id === VideoState.TRANSCODING_FAILED) { | 181 | if (video.state.id === VideoState.TRANSCODING_FAILED) { |
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.html b/client/src/app/shared/shared-video-miniature/videos-list.component.html index 2b554517f..c220f61f1 100644 --- a/client/src/app/shared/shared-video-miniature/videos-list.component.html +++ b/client/src/app/shared/shared-video-miniature/videos-list.component.html | |||
@@ -12,15 +12,15 @@ | |||
12 | 12 | ||
13 | <div class="action-block"> | 13 | <div class="action-block"> |
14 | <ng-container *ngFor="let action of headerActions"> | 14 | <ng-container *ngFor="let action of headerActions"> |
15 | <a *ngIf="action.routerLink" class="ml-2" [routerLink]="action.routerLink" routerLinkActive="active"> | 15 | <a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active"> |
16 | <ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container> | 16 | <ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container> |
17 | </a> | 17 | </a> |
18 | 18 | ||
19 | <a *ngIf="!action.routerLink && !action.href && action.click" class="ml-2" (click)="action.click($event)" (key.enter)="action.click($event)"> | 19 | <a *ngIf="!action.routerLink && !action.href && action.click" class="ms-2" (click)="action.click($event)" (key.enter)="action.click($event)"> |
20 | <ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container> | 20 | <ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container> |
21 | </a> | 21 | </a> |
22 | 22 | ||
23 | <a *ngIf="!action.routerLink && action.href && action.click" class="ml-2" (click)="action.click($event)" (key.enter)="action.click($event)" [href]="action.href"> | 23 | <a *ngIf="!action.routerLink && action.href && action.click" class="ms-2" (click)="action.click($event)" (key.enter)="action.click($event)" [href]="action.href"> |
24 | <ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container> | 24 | <ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container> |
25 | </a> | 25 | </a> |
26 | 26 | ||
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.scss b/client/src/app/shared/shared-video-miniature/videos-list.component.scss index 209201a5c..fb9dcafb8 100644 --- a/client/src/app/shared/shared-video-miniature/videos-list.component.scss +++ b/client/src/app/shared/shared-video-miniature/videos-list.component.scss | |||
@@ -54,9 +54,9 @@ $margin-top: 30px; | |||
54 | } | 54 | } |
55 | 55 | ||
56 | .date-title { | 56 | .date-title { |
57 | font-size: 16px; | ||
58 | font-weight: $font-semibold; | 57 | font-weight: $font-semibold; |
59 | margin-bottom: 20px; | 58 | margin-bottom: 20px; |
59 | font-size: 1rem; | ||
60 | 60 | ||
61 | // Make the element span a full grid row within .videos grid | 61 | // Make the element span a full grid row within .videos grid |
62 | grid-column: 1 / -1; | 62 | grid-column: 1 / -1; |
@@ -99,11 +99,5 @@ $margin-top: 30px; | |||
99 | align-items: center; | 99 | align-items: center; |
100 | height: auto; | 100 | height: auto; |
101 | margin-bottom: 10px; | 101 | margin-bottom: 10px; |
102 | |||
103 | .title-page { | ||
104 | @include margin-right(0); | ||
105 | |||
106 | margin-bottom: 10px; | ||
107 | } | ||
108 | } | 102 | } |
109 | } | 103 | } |
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html index bd5d37196..6d787796a 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html | |||
@@ -30,12 +30,10 @@ | |||
30 | </div> | 30 | </div> |
31 | 31 | ||
32 | <div class="optional-rows" *ngIf="playlist.optionalRowDisplayed"> | 32 | <div class="optional-rows" *ngIf="playlist.optionalRowDisplayed"> |
33 | <div class="labels"> | 33 | <div class="header-label" i18n>Start at</div> |
34 | <div i18n>Start at</div> | 34 | <div class="header-label" i18n>Stop at</div> |
35 | <div i18n>Stop at</div> | ||
36 | </div> | ||
37 | 35 | ||
38 | <div *ngFor="let element of buildOptionalRowElements(playlist)"> | 36 | <ng-container *ngFor="let element of buildOptionalRowElements(playlist)"> |
39 | <my-peertube-checkbox | 37 | <my-peertube-checkbox |
40 | [inputName]="getOptionalInputName(playlist, element)" | 38 | [inputName]="getOptionalInputName(playlist, element)" |
41 | [ngModel]="element.enabled" [onPushWorkaround]="true" | 39 | [ngModel]="element.enabled" [onPushWorkaround]="true" |
@@ -55,7 +53,7 @@ | |||
55 | (inputBlur)="onElementTimestampUpdate(playlist, element)" | 53 | (inputBlur)="onElementTimestampUpdate(playlist, element)" |
56 | #stopAt | 54 | #stopAt |
57 | ></my-timestamp-input> | 55 | ></my-timestamp-input> |
58 | </div> | 56 | </ng-container> |
59 | </div> | 57 | </div> |
60 | </div> | 58 | </div> |
61 | </div> | 59 | </div> |
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss index 7db469d7c..de2f1032b 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss | |||
@@ -1,10 +1,6 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | $optional-rows-checkbox-width: 34px; | ||
5 | $timestamp-width: 50px; | ||
6 | $timestamp-margin-right: 10px; | ||
7 | |||
8 | .header, | 4 | .header, |
9 | .dropdown-item, | 5 | .dropdown-item, |
10 | .input-container { | 6 | .input-container { |
@@ -52,12 +48,12 @@ $timestamp-margin-right: 10px; | |||
52 | } | 48 | } |
53 | } | 49 | } |
54 | 50 | ||
55 | .primary-row, | 51 | .primary-row { |
56 | .optional-rows > div { | ||
57 | display: flex; | 52 | display: flex; |
58 | 53 | ||
59 | my-peertube-checkbox { | 54 | my-peertube-checkbox { |
60 | @include margin-right(10px); | 55 | @include margin-right(10px); |
56 | |||
61 | align-self: center; | 57 | align-self: center; |
62 | } | 58 | } |
63 | 59 | ||
@@ -84,41 +80,30 @@ $timestamp-margin-right: 10px; | |||
84 | height: 19px; | 80 | height: 19px; |
85 | } | 81 | } |
86 | } | 82 | } |
87 | |||
88 | my-timestamp-input { | ||
89 | @include margin-right($timestamp-margin-right); | ||
90 | |||
91 | ::ng-deep .ui-inputtext { | ||
92 | padding: 0; | ||
93 | width: $timestamp-width; | ||
94 | } | ||
95 | } | ||
96 | } | 83 | } |
97 | 84 | ||
98 | .optional-rows { | 85 | .optional-rows { |
99 | > div { | 86 | display: grid; |
100 | padding: 8px 5px 5px 10px; | 87 | grid-template-columns: 35px 80px 80px; |
101 | } | 88 | row-gap: 3px; |
89 | column-gap: 10px; | ||
90 | align-items: center; | ||
102 | 91 | ||
103 | my-peertube-checkbox { | 92 | my-peertube-checkbox { |
104 | @include margin-right(0 !important); | 93 | @include margin-left(auto); |
105 | |||
106 | display: block; | ||
107 | width: $optional-rows-checkbox-width; | ||
108 | } | 94 | } |
109 | 95 | ||
110 | .labels { | 96 | .header-label { |
111 | @include margin-left($optional-rows-checkbox-width); | ||
112 | |||
113 | font-size: 13px; | 97 | font-size: 13px; |
114 | color: pvar(--greyForegroundColor); | 98 | color: pvar(--greyForegroundColor); |
115 | padding-top: 5px; | 99 | padding-left: 2px; |
116 | padding-bottom: 0; | ||
117 | 100 | ||
118 | div { | 101 | &:nth-child(1) { |
119 | @include margin-right($timestamp-margin-right); | 102 | grid-column: 2; |
103 | } | ||
120 | 104 | ||
121 | width: $timestamp-width; | 105 | &:nth-child(2) { |
106 | grid-column: 3; | ||
122 | } | 107 | } |
123 | } | 108 | } |
124 | } | 109 | } |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html index 2400a4c25..f58d5f7f6 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html | |||
@@ -21,7 +21,7 @@ | |||
21 | [attr.title]="playlistElement.video.name" | 21 | [attr.title]="playlistElement.video.name" |
22 | >{{ playlistElement.video.name }}</a> | 22 | >{{ playlistElement.video.name }}</a> |
23 | 23 | ||
24 | <span *ngIf="isVideoPrivate()" class="badge badge-yellow">Private</span> | 24 | <span *ngIf="isVideoPrivate()" class="pt-badge badge-yellow">Private</span> |
25 | </div> | 25 | </div> |
26 | 26 | ||
27 | <span class="video-miniature-created-at-views"> | 27 | <span class="video-miniature-created-at-views"> |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss index fbf67e892..e6b01d33d 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss | |||
@@ -75,41 +75,6 @@ my-video-thumbnail, | |||
75 | left: -2px; | 75 | left: -2px; |
76 | } | 76 | } |
77 | } | 77 | } |
78 | |||
79 | .video-info { | ||
80 | display: flex; | ||
81 | flex-direction: column; | ||
82 | align-self: flex-start; | ||
83 | min-width: 0; | ||
84 | |||
85 | .video-info-header { | ||
86 | display: flex; | ||
87 | align-items: baseline; | ||
88 | |||
89 | a { | ||
90 | width: auto; | ||
91 | padding-right: 5px; | ||
92 | } | ||
93 | |||
94 | .badge { | ||
95 | @include peertube-badge; | ||
96 | margin-right: 5px; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | .video-info-account, | ||
101 | .video-info-timestamp { | ||
102 | color: pvar(--greyForegroundColor); | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | .video-info-name { | ||
108 | @include ellipsis; | ||
109 | |||
110 | font-size: 18px; | ||
111 | font-weight: $font-semibold; | ||
112 | display: inline-block; | ||
113 | } | 78 | } |
114 | 79 | ||
115 | .more, | 80 | .more, |
@@ -140,6 +105,45 @@ my-video-thumbnail, | |||
140 | } | 105 | } |
141 | } | 106 | } |
142 | 107 | ||
108 | .video-info-name { | ||
109 | @include ellipsis; | ||
110 | |||
111 | font-size: 18px; | ||
112 | font-weight: $font-semibold; | ||
113 | display: inline-block; | ||
114 | } | ||
115 | |||
116 | .video-info { | ||
117 | display: flex; | ||
118 | flex-direction: column; | ||
119 | align-self: flex-start; | ||
120 | min-width: 0; | ||
121 | |||
122 | .video-info-header { | ||
123 | display: flex; | ||
124 | align-items: baseline; | ||
125 | |||
126 | a { | ||
127 | width: auto; | ||
128 | padding-right: 5px; | ||
129 | } | ||
130 | |||
131 | .pt-badge { | ||
132 | @include margin-right(5px); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | .video-info-account, | ||
137 | .video-info-timestamp { | ||
138 | color: pvar(--greyForegroundColor); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | .video-info-account, | ||
143 | .video-miniature-created-at-views { | ||
144 | font-size: 14px; | ||
145 | } | ||
146 | |||
143 | .dropdown-menu { | 147 | .dropdown-menu { |
144 | 148 | ||
145 | .dropdown-item { | 149 | .dropdown-item { |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss index 3956d9282..d43afad28 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss | |||
@@ -53,7 +53,7 @@ | |||
53 | 53 | ||
54 | .privacy-date { | 54 | .privacy-date { |
55 | margin-top: 5px; | 55 | margin-top: 5px; |
56 | font-size: 13px; | 56 | font-size: 14px; |
57 | 57 | ||
58 | .privacy { | 58 | .privacy { |
59 | font-weight: $font-semibold; | 59 | font-weight: $font-semibold; |