aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+video-channels/video-channel-about/video-channel-about.component.html22
-rw-r--r--client/src/app/+video-channels/video-channel-about/video-channel-about.component.scss12
-rw-r--r--client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts43
-rw-r--r--client/src/app/+video-channels/video-channels-routing.module.ts10
-rw-r--r--client/src/app/+video-channels/video-channels.component.html128
-rw-r--r--client/src/app/+video-channels/video-channels.component.scss358
-rw-r--r--client/src/app/+video-channels/video-channels.component.ts46
-rw-r--r--client/src/app/+video-channels/video-channels.module.ts2
-rw-r--r--client/src/sass/application.scss25
-rw-r--r--client/src/sass/include/_mixins.scss50
-rw-r--r--client/src/sass/include/_variables.scss7
11 files changed, 507 insertions, 196 deletions
diff --git a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.html b/client/src/app/+video-channels/video-channel-about/video-channel-about.component.html
deleted file mode 100644
index 8dff8ba91..000000000
--- a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.html
+++ /dev/null
@@ -1,22 +0,0 @@
1<div class="margin-content">
2 <div *ngIf="videoChannel" class="row no-gutters">
3 <div class="description col-md-6 col-sm-12 pr-2">
4 <div class="block">
5 <div i18n class="small-title">DESCRIPTION</div>
6 <div class="content" [innerHtml]="getVideoChannelDescription()"></div>
7 </div>
8
9 <div class="block" *ngIf="supportHTML">
10 <div i18n class="small-title">SUPPORT THIS CHANNEL</div>
11 <div class="content" [innerHtml]="supportHTML"></div>
12 </div>
13 </div>
14
15 <div class="stats col-md-6 col-sm-12">
16 <div class="block">
17 <div i18n class="small-title">STATS</div>
18 <div i18n class="content">Created {{ videoChannel.createdAt | date }}</div>
19 </div>
20 </div>
21 </div>
22</div>
diff --git a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.scss b/client/src/app/+video-channels/video-channel-about/video-channel-about.component.scss
deleted file mode 100644
index 5bcd4b561..000000000
--- a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.scss
+++ /dev/null
@@ -1,12 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4.block {
5 margin-bottom: 40px;
6
7 .small-title {
8 @include in-content-small-title;
9
10 margin-bottom: 20px;
11 }
12}
diff --git a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts b/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts
deleted file mode 100644
index 537c7d08e..000000000
--- a/client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts
+++ /dev/null
@@ -1,43 +0,0 @@
1import { Subscription } from 'rxjs'
2import { Component, OnDestroy, OnInit } from '@angular/core'
3import { MarkdownService } from '@app/core'
4import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
5
6@Component({
7 selector: 'my-video-channel-about',
8 templateUrl: './video-channel-about.component.html',
9 styleUrls: [ './video-channel-about.component.scss' ]
10})
11export class VideoChannelAboutComponent implements OnInit, OnDestroy {
12 videoChannel: VideoChannel
13 descriptionHTML = ''
14 supportHTML = ''
15
16 private videoChannelSub: Subscription
17
18 constructor (
19 private videoChannelService: VideoChannelService,
20 private markdownService: MarkdownService
21 ) { }
22
23 ngOnInit () {
24 // Parent get the video channel for us
25 this.videoChannelSub = this.videoChannelService.videoChannelLoaded
26 .subscribe(async videoChannel => {
27 this.videoChannel = videoChannel
28
29 this.descriptionHTML = await this.markdownService.textMarkdownToHTML(this.videoChannel.description)
30 this.supportHTML = await this.markdownService.enhancedMarkdownToHTML(this.videoChannel.support)
31 })
32 }
33
34 ngOnDestroy () {
35 if (this.videoChannelSub) this.videoChannelSub.unsubscribe()
36 }
37
38 getVideoChannelDescription () {
39 if (this.descriptionHTML) return this.descriptionHTML
40
41 return $localize`No description`
42 }
43}
diff --git a/client/src/app/+video-channels/video-channels-routing.module.ts b/client/src/app/+video-channels/video-channels-routing.module.ts
index f8c32f14e..fcaad8934 100644
--- a/client/src/app/+video-channels/video-channels-routing.module.ts
+++ b/client/src/app/+video-channels/video-channels-routing.module.ts
@@ -1,7 +1,6 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router' 2import { RouterModule, Routes } from '@angular/router'
3import { MetaGuard } from '@ngx-meta/core' 3import { MetaGuard } from '@ngx-meta/core'
4import { VideoChannelAboutComponent } from './video-channel-about/video-channel-about.component'
5import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component' 4import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component'
6import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' 5import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
7import { VideoChannelsComponent } from './video-channels.component' 6import { VideoChannelsComponent } from './video-channels.component'
@@ -38,15 +37,6 @@ const videoChannelsRoutes: Routes = [
38 title: $localize`Video channel playlists` 37 title: $localize`Video channel playlists`
39 } 38 }
40 } 39 }
41 },
42 {
43 path: 'about',
44 component: VideoChannelAboutComponent,
45 data: {
46 meta: {
47 title: $localize`About video channel`
48 }
49 }
50 } 40 }
51 ] 41 ]
52 } 42 }
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index b3ea19768..f63110bf5 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -1,50 +1,114 @@
1<div *ngIf="videoChannel" class="row"> 1<div class="root" *ngIf="videoChannel">
2 <div class="sub-menu"> 2 <div class="channel-info">
3 3
4 <div class="actor"> 4 <ng-template #buttonsTemplate>
5 <img [src]="videoChannel.avatarUrl" alt="Avatar" /> 5 <a *ngIf="isManageable() && !isInSmallView()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n>
6 Manage channel
7 </a>
8
9 <my-subscribe-button #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
10 </ng-template>
11
12 <ng-template #ownerTemplate>
13 <div class="owner-block">
14 <div class="avatar-row">
15 <img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" />
16
17 <div class="actor-info">
18 <h4>{{ videoChannel.ownerAccount.displayName }}</h4>
6 19
7 <div class="actor-info"> 20 <div class="actor-handle">@{{ videoChannel.ownerBy }}</div>
8 <div class="actor-names">
9 <div class="actor-display-name">{{ videoChannel.displayName }}</div>
10 <div class="actor-name">
11 <span>{{ videoChannel.nameWithHost }}</span>
12 <button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()"
13 class="btn btn-outline-secondary btn-sm copy-button"
14 >
15 <span class="glyphicon glyphicon-duplicate"></span>
16 </button>
17 </div> 21 </div>
18 </div> 22 </div>
19 23
20 <div class="right-buttons"> 24 <div class="owner-description">
21 <a *ngIf="isChannelManageable && !isInSmallView" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="btn btn-outline-tertiary mr-2" i18n> 25 <div class="description-html" [innerHTML]="ownerDescriptionHTML"></div>
22 Manage channel
23 </a>
24 <my-subscribe-button #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
25 </div> 26 </div>
26 27
27 <div class="actor-lower"> 28 <a class="view-account short" [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n>
28 <div class="actor-followers" i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> 29 View account
30 </a>
31
32 <a class="view-account complete" [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n>
33 View owner account
34 </a>
35 </div>
36 </ng-template>
37
38 <div class="channel-avatar-row">
39 <img [src]="videoChannel.avatarUrl" alt="Avatar" />
40
41 <div>
42 <div class="section-label" i18n>VIDEO CHANNEL</div>
43
44 <div class="actor-info">
45 <div>
46 <div class="actor-display-name">
47 <h1>{{ videoChannel.displayName }}</h1>
48 </div>
49
50 <div class="actor-handle">
51 <span>@{{ videoChannel.nameWithHost }}</span>
52 <button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()"
53 class="btn btn-outline-secondary btn-sm copy-button" title="Copy channel handle" i18n-title
54 >
55 <span class="glyphicon glyphicon-duplicate"></span>
56 </button>
57 </div>
58
59 <div class="actor-counters">
60 <span i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</span>
61
62 <span class="videos-count" *ngIf="channelVideosCount !== undefined" i18n>
63 {channelVideosCount, plural, =1 {1 videos} other {{{ channelVideosCount }} videos}}
64 </span>
65 </div>
66 </div>
29 67
30 <a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Go the owner account page" class="actor-owner"> 68 <div class="channel-buttons right">
31 <span class="d-inline-flex"><span i18n class="d-none d-sm-block mr-1">Created by</span>{{ videoChannel.ownerBy }}</span> 69 <ng-template *ngTemplateOutlet="buttonsTemplate"></ng-template>
32 <img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" /> 70 </div>
33 </a>
34 </div> 71 </div>
35 </div> 72 </div>
36 </div> 73 </div>
37 74
38 <div class="links w-100"> 75 <div class="channel-description" [ngClass]="{ expanded: channelDescriptionExpanded }">
39 <ng-template #linkTemplate let-item="item"> 76 <div class="description-html" [innerHTML]="channelDescriptionHTML"></div>
40 <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> 77
41 </ng-template> 78 <div class="created-at" i18n>Channel created on {{ videoChannel.createdAt | date }}</div>
79 </div>
42 80
43 <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow> 81 <div *ngIf="!channelDescriptionExpanded" class="show-more" role="button"
82 (click)="channelDescriptionExpanded = !channelDescriptionExpanded"
83 title="Show the complete description" i18n-title i18n
84 >
85 Show more...
44 </div> 86 </div>
87
88 <div class="channel-buttons bottom">
89 <ng-template *ngTemplateOutlet="buttonsTemplate"></ng-template>
90 </div>
91
92 <div class="owner-card">
93 <div class="section-label" i18n>OWNER ACCOUNT</div>
94
95 <ng-template *ngTemplateOutlet="ownerTemplate"></ng-template>
96 </div>
97 </div>
98
99 <div class="bottom-owner">
100 <div class="section-label" i18n>OWNER ACCOUNT</div>
101
102 <ng-template *ngTemplateOutlet="ownerTemplate"></ng-template>
45 </div> 103 </div>
46 104
47 <div class="margin-content"> 105 <div class="links">
48 <router-outlet></router-outlet> 106 <ng-template #linkTemplate let-item="item">
107 <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
108 </ng-template>
109
110 <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow>
49 </div> 111 </div>
112
113 <router-outlet></router-outlet>
50</div> 114</div>
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss
index 22f21dcc6..16e13c578 100644
--- a/client/src/app/+video-channels/video-channels.component.scss
+++ b/client/src/app/+video-channels/video-channels.component.scss
@@ -1,89 +1,345 @@
1// Bootstrap grid utilities require functions, variables and mixins
2@import 'node_modules/bootstrap/scss/functions';
3@import 'node_modules/bootstrap/scss/variables';
4@import 'node_modules/bootstrap/scss/mixins';
5@import 'node_modules/bootstrap/scss/grid';
6
7@import '_variables'; 1@import '_variables';
8@import '_mixins'; 2@import '_mixins';
3@import '_miniature';
9 4
10.sub-menu { 5.root {
11 @include sub-menu-with-actor; 6 --myGlobalPadding: 60px;
7 --myChannelImgMargin: 30px;
8 --myFontSize: 16px;
9 --myGreyChannelFontSize: 16px;
10 --myGreyOwnerFontSize: 14px;
11}
12 12
13 .actor, .actor-info { 13.section-label {
14 width: 100%; 14 color: pvar(--mainColor);
15 font-size: 12px;
16 margin-bottom: 15px;
17 font-weight: $font-bold;
18 letter-spacing: 2.5px;
19}
20
21.links {
22 @include fluid-videos-miniature-layout;
23}
24
25.channel-info {
26 display: grid;
27 grid-template-columns: 1fr auto;
28 grid-template-rows: auto auto;
29
30 background-color: pvar(--channelBackgroundColor);
31 margin-bottom: 45px;
32 padding: var(--myGlobalPadding) var(--myGlobalPadding) 0 var(--myGlobalPadding);
33 font-size: var(--myFontSize);
34}
35
36.channel-avatar-row {
37 display: flex;
38 grid-column: 1;
39 margin-bottom: 30px;
40
41 img {
42 @include channel-avatar(120px);
43 }
44
45 > div {
46 margin-left: var(--myChannelImgMargin);
15 } 47 }
16 48
17 .actor-info { 49 .actor-info {
18 display: grid !important; 50 display: flex;
19 grid-template-columns: 1fr auto;
20 grid-template-rows: 1fr auto / 1fr auto;
21 grid-template-areas: "name buttons" "lower buttons";
22 51
23 @include media-breakpoint-down(lg) { 52 > div:first-child {
24 grid-template-areas: "name name" "lower buttons"; 53 flex-grow: 1;
25 } 54 }
26 } 55 }
27 56
28 .actor-names { 57 .actor-display-name {
29 grid-area: name; 58 display: flex;
59 flex-wrap: wrap;
30 } 60 }
31 61
32 .actor-name { 62 h1 {
33 flex-grow: 1; 63 font-size: 28px;
64 font-weight: $font-bold;
65 margin: 0;
66 }
34 67
35 .copy-button { 68 .actor-handle,
36 border: none; 69 .actor-counters {
37 padding: 5px; 70 color: pvar(--greyForegroundColor);
38 margin-top: -2px; 71 font-size: var(--myGreyChannelFontSize);
39 } 72 }
73
74 .actor-counters > *:not(:last-child)::after {
75 content: '•';
76 margin: 0 10px;
77 color: pvar(--mainColor);
40 } 78 }
41} 79}
42 80
43.margin-content { 81.channel-description {
44 // margin-content is required, but child views have their own margins 82 grid-column: 1;
45 // that match views outside the scope of accounts, so we only align
46 // them with the margins of .sub-menu when required.
47 margin: 0;
48} 83}
49 84
50.right-buttons { 85.show-more {
86 display: none;
87 color: pvar(--mainColor);
88 cursor: pointer;
89 margin: 10px auto 45px auto;
90}
91
92
93.channel-buttons {
51 display: flex; 94 display: flex;
52 height: max-content; 95 flex-wrap: wrap;
53 margin-left: auto; 96
54 margin-top: 10px; 97 > *:not(:last-child) {
98 margin-right: 15px;
99 }
100}
101
102.channel-buttons.right {
103 margin-left: 45px;
104}
105
106// Only used by mobile
107.channel-buttons.bottom {
108 display: none;
109}
110
111.created-at {
112 margin-top: 15px;
113 color: pvar(--greyForegroundColor);
114 padding-bottom: 60px;
115}
116
117.owner-card {
118 margin-left: 105px;
119 grid-column: 2;
120 // Takes all the column
121 grid-row: 1 / 3;
122 place-self: end;
123}
124
125// Only used on mobile
126.bottom-owner {
127 display: none;
128}
129
130.owner-block {
131 background-color: pvar(--mainBackgroundColor);
132 padding: 30px;
133 width: 300px;
134 font-size: var(--myFontSize);
135
136 .avatar-row {
137 display: flex;
138 margin-bottom: 15px;
139
140 img {
141 @include avatar(48px);
142 }
143
144 .actor-info {
145 margin-left: 15px;
146 }
147
148 h4 {
149 font-size: 18px;
150 margin: 0;
151 }
152
153 .actor-handle {
154 font-size: var(--myGreyOwnerFontSize);
155 color: pvar(--greyForegroundColor);
156 }
157 }
158
159 .owner-description {
160 height: 140px;
161
162 @include fade-text(120px, pvar(--mainBackgroundColor));
163 }
164}
165
166.view-account.short {
167 @include peertube-button-link;
168 @include orange-button-inverted;
169
170 margin-top: 30px;
171}
172
173.view-account.complete {
174 display: none;
175}
176
177.copy-button {
178 border: none;
179}
180
181@media screen and (max-width: 1400px) {
182 // Takes all the row width
183 .channel-avatar-row {
184 grid-column: 1 / 3;
185 }
186
187 .owner-card {
188 grid-row: 2;
189 margin-left: 60px;
190 }
191}
192
193@media screen and (max-width: 1100px) {
194 .root {
195 --myGlobalPadding: 45px;
196 --myChannelImgMargin: 15px;
197 }
198
199 .channel-info {
200 display: flex;
201 flex-direction: column;
202 margin-bottom: 0;
203 }
204
205 .channel-description:not(.expanded) {
206 max-height: 70px;
207
208 @include fade-text(30px, pvar(--channelBackgroundColor));
209 }
210
211 .show-more {
212 display: inline-block;
213 }
214
215 .channel-buttons.bottom {
216 display: flex;
217 justify-content: center;
218 margin-bottom: 30px;
219 }
220
221 .channel-buttons.right {
222 display: none;
223 }
224
225 .owner-card {
226 display: none;
227 }
55 228
56 grid-row: buttons-start / span buttons-end; 229 .bottom-owner {
57 grid-column: buttons-start; 230 display: block;
231 width: 100%;
232 border-bottom: 2px solid $separator-border-color;
233 padding: var(--myGlobalPadding) 45px;
234 margin-bottom: 60px;
235 }
58 236
59 @include media-breakpoint-down(lg) { 237 .owner-block {
60 flex-flow: column-reverse; 238 display: grid;
239 width: 100%;
240 padding: 0;
61 241
62 a { 242 .avatar-row {
63 margin-top: 0.25rem; 243 grid-column: 1;
64 margin-right: 0 !important; 244 margin-right: 30px;
245 }
246
247 .owner-description {
248 grid-column: 2;
249 max-height: 70px;
250
251 @include fade-text(30px, pvar(--mainBackgroundColor));
252 }
253
254 .view-account {
255 grid-column: 2;
65 } 256 }
66 } 257 }
67 258
68 a { 259 .view-account.complete {
69 @include peertube-button-outline; 260 display: inline-block;
70 line-height: 1.8; 261 margin-top: 10px;
262 color: pvar(--mainColor);
71 } 263 }
72 264
73 my-subscribe-button { 265 .view-account.short {
74 height: min-content; 266 display: none;
75 } 267 }
76} 268}
77 269
78@media screen and (max-width: $mobile-view) { 270@media screen and (max-width: $mobile-view) {
79 .sub-menu { 271 .root {
80 .actor { 272 --myGlobalPadding: 15px;
81 flex-direction: column; 273 --myFontSize: 14px;
274 --myGreyChannelFontSize: 13px;
275 --myGreyOwnerFontSize: 13px;
276 }
277
278 .links {
279 margin: auto !important;
280 width: min-content;
281 }
282
283 .section-label {
284 font-size: 10px;
285 letter-spacing: 2.1px;
286 margin-bottom: 5px;
287 }
288
289 .channel-avatar-row {
290 margin-bottom: 15px;
291
292 h1 {
293 font-size: 22px;
294 }
295
296 img {
297 @include channel-avatar(80px);
298 }
299 }
300
301 .show-more {
302 margin-bottom: 30px;
303 }
82 304
83 .actor-info .actor-names { 305 .bottom-owner {
306 padding: 15px;
307 margin-bottom: 30px;
308
309 .section-label {
310 display: none;
311 }
312 }
313
314 .owner-block {
315 display: block;
316
317 .avatar-row {
318 display: flex;
319 flex-direction: row-reverse;
320 margin: 0;
321
322 h4 {
323 font-size: 16px;
324 }
325
326 .actor-info {
327 display: flex;
84 flex-direction: column; 328 flex-direction: column;
85 align-items: normal; 329 align-items: flex-end;
330 justify-content: flex-end;
331 margin-top: -5px;
332 }
333
334 img {
335 @include channel-avatar(64px);
336
337 margin: -30px 0 0 15px;
86 } 338 }
87 } 339 }
340
341 .owner-description {
342 display: none;
343 }
88 } 344 }
89} 345}
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts
index bb601e227..037c108f2 100644
--- a/client/src/app/+video-channels/video-channels.component.ts
+++ b/client/src/app/+video-channels/video-channels.component.ts
@@ -3,8 +3,8 @@ import { Subscription } from 'rxjs'
3import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators' 3import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators'
4import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' 4import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
5import { ActivatedRoute } from '@angular/router' 5import { ActivatedRoute } from '@angular/router'
6import { AuthService, Notifier, RestExtractor, ScreenService } from '@app/core' 6import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } from '@app/core'
7import { ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main' 7import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
8import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' 8import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
9import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 9import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
10 10
@@ -20,6 +20,11 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
20 links: ListOverflowItem[] = [] 20 links: ListOverflowItem[] = []
21 isChannelManageable = false 21 isChannelManageable = false
22 22
23 channelVideosCount: number
24 ownerDescriptionHTML = ''
25 channelDescriptionHTML = ''
26 channelDescriptionExpanded = false
27
23 private routeSub: Subscription 28 private routeSub: Subscription
24 29
25 constructor ( 30 constructor (
@@ -27,9 +32,11 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
27 private notifier: Notifier, 32 private notifier: Notifier,
28 private authService: AuthService, 33 private authService: AuthService,
29 private videoChannelService: VideoChannelService, 34 private videoChannelService: VideoChannelService,
35 private videoService: VideoService,
30 private restExtractor: RestExtractor, 36 private restExtractor: RestExtractor,
31 private hotkeysService: HotkeysService, 37 private hotkeysService: HotkeysService,
32 private screenService: ScreenService 38 private screenService: ScreenService,
39 private markdown: MarkdownService
33 ) { } 40 ) { }
34 41
35 ngOnInit () { 42 ngOnInit () {
@@ -43,16 +50,14 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
43 HttpStatusCode.NOT_FOUND_404 50 HttpStatusCode.NOT_FOUND_404
44 ])) 51 ]))
45 ) 52 )
46 .subscribe(videoChannel => { 53 .subscribe(async videoChannel => {
54 this.channelDescriptionHTML = await this.markdown.textMarkdownToHTML(videoChannel.description)
55 this.ownerDescriptionHTML = await this.markdown.textMarkdownToHTML(videoChannel.ownerAccount.description)
56
57 // After the markdown renderer to avoid layout changes
47 this.videoChannel = videoChannel 58 this.videoChannel = videoChannel
48 59
49 if (this.authService.isLoggedIn()) { 60 this.loadChannelVideosCount()
50 this.authService.userInformationLoaded
51 .subscribe(() => {
52 const channelUserId = this.videoChannel.ownerAccount.userId
53 this.isChannelManageable = channelUserId && channelUserId === this.authService.getUser().id
54 })
55 }
56 }) 61 })
57 62
58 this.hotkeys = [ 63 this.hotkeys = [
@@ -67,8 +72,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
67 72
68 this.links = [ 73 this.links = [
69 { label: $localize`VIDEOS`, routerLink: 'videos' }, 74 { label: $localize`VIDEOS`, routerLink: 'videos' },
70 { label: $localize`VIDEO PLAYLISTS`, routerLink: 'video-playlists' }, 75 { label: $localize`VIDEO PLAYLISTS`, routerLink: 'video-playlists' }
71 { label: $localize`ABOUT`, routerLink: 'about' }
72 ] 76 ]
73 } 77 }
74 78
@@ -79,7 +83,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
79 if (this.isUserLoggedIn()) this.hotkeysService.remove(this.hotkeys) 83 if (this.isUserLoggedIn()) this.hotkeysService.remove(this.hotkeys)
80 } 84 }
81 85
82 get isInSmallView () { 86 isInSmallView () {
83 return this.screenService.isInSmallView() 87 return this.screenService.isInSmallView()
84 } 88 }
85 89
@@ -87,12 +91,24 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
87 return this.authService.isLoggedIn() 91 return this.authService.isLoggedIn()
88 } 92 }
89 93
90 get isManageable () { 94 isManageable () {
91 if (!this.isUserLoggedIn()) return false 95 if (!this.isUserLoggedIn()) return false
96
92 return this.videoChannel.ownerAccount.userId === this.authService.getUser().id 97 return this.videoChannel.ownerAccount.userId === this.authService.getUser().id
93 } 98 }
94 99
95 activateCopiedMessage () { 100 activateCopiedMessage () {
96 this.notifier.success($localize`Username copied`) 101 this.notifier.success($localize`Username copied`)
97 } 102 }
103
104 private loadChannelVideosCount () {
105 this.videoService.getVideoChannelVideos({
106 videoChannel: this.videoChannel,
107 videoPagination: {
108 currentPage: 1,
109 itemsPerPage: 0
110 },
111 sort: '-publishedAt'
112 }).subscribe(res => this.channelVideosCount = res.total)
113 }
98} 114}
diff --git a/client/src/app/+video-channels/video-channels.module.ts b/client/src/app/+video-channels/video-channels.module.ts
index 05236ff85..1b58a1d92 100644
--- a/client/src/app/+video-channels/video-channels.module.ts
+++ b/client/src/app/+video-channels/video-channels.module.ts
@@ -5,7 +5,6 @@ import { SharedMainModule } from '@app/shared/shared-main'
5import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' 5import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription'
6import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' 6import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
7import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' 7import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist'
8import { VideoChannelAboutComponent } from './video-channel-about/video-channel-about.component'
9import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component' 8import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component'
10import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' 9import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
11import { VideoChannelsRoutingModule } from './video-channels-routing.module' 10import { VideoChannelsRoutingModule } from './video-channels-routing.module'
@@ -26,7 +25,6 @@ import { VideoChannelsComponent } from './video-channels.component'
26 declarations: [ 25 declarations: [
27 VideoChannelsComponent, 26 VideoChannelsComponent,
28 VideoChannelVideosComponent, 27 VideoChannelVideosComponent,
29 VideoChannelAboutComponent,
30 VideoChannelPlaylistsComponent 28 VideoChannelPlaylistsComponent
31 ], 29 ],
32 30
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index a0009eecc..0a92afef3 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -36,7 +36,9 @@ body {
36 36
37 --menuBackgroundColor: #{$menu-background}; 37 --menuBackgroundColor: #{$menu-background};
38 --menuForegroundColor: #{$menu-color}; 38 --menuForegroundColor: #{$menu-color};
39
39 --submenuColor: #{$sub-menu-color}; 40 --submenuColor: #{$sub-menu-color};
41 --channelBackgroundColor: #{$channel-background-color};
40 42
41 --inputForegroundColor: #{$input-foreground-color}; 43 --inputForegroundColor: #{$input-foreground-color};
42 --inputBackgroundColor: #{$input-background-color}; 44 --inputBackgroundColor: #{$input-background-color};
@@ -277,11 +279,6 @@ my-input-toggle-hidden ::ng-deep input {
277 font-weight: bold; 279 font-weight: bold;
278} 280}
279 281
280@keyframes spin {
281 from { transform: scale(1) rotate(0deg);}
282 to { transform: scale(1) rotate(360deg);}
283}
284
285// In tables, don't have a hover different background 282// In tables, don't have a hover different background
286table { 283table {
287 .action-button-edit, .action-button-delete { 284 .action-button-edit, .action-button-delete {
@@ -468,3 +465,21 @@ ngx-loading-bar {
468 } 465 }
469 } 466 }
470} 467}
468
469// Utils
470
471.peertube-button {
472 @include peertube-button;
473}
474
475.peertube-button-link {
476 @include peertube-button-link;
477}
478
479.orange-button {
480 @include orange-button;
481}
482
483.orange-button-inverted {
484 @include orange-button-inverted;
485}
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index cf5ac8fd8..82c60a59d 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -31,9 +31,19 @@
31 text-overflow: ellipsis; 31 text-overflow: ellipsis;
32} 32}
33 33
34@mixin prefix($property, $parameters...) { 34@mixin fade-text ($fade-after, $background-color) {
35 @each $prefix in -webkit-, -moz-, -ms-, -o-, "" { 35 position: relative;
36 #{$prefix}#{$property}: $parameters; 36 overflow: hidden;
37
38 &:after {
39 content: '';
40 pointer-events: none;
41 width: 100%;
42 height: 100%;
43 position: absolute;
44 left: 0;
45 top: 0;
46 background: linear-gradient(transparent $fade-after, $background-color);
37 } 47 }
38} 48}
39 49
@@ -138,6 +148,33 @@
138 } 148 }
139} 149}
140 150
151@mixin orange-button-inverted {
152 @include button-focus(pvar(--mainColorLightest));
153
154 border: 2px solid pvar(--mainColor);
155 font-weight: $font-regular;
156
157 &, &:active, &:focus {
158 color: pvar(--mainColor);
159 background-color: pvar(--mainBackgroundColor);
160 }
161
162 &:hover {
163 color: pvar(--mainColor);
164 background-color: pvar(--mainColorLightest);
165 }
166
167 &[disabled], &.disabled {
168 cursor: default;
169 color: pvar(--mainColor);
170 background-color: #C6C6C6;
171 }
172
173 my-global-icon {
174 @include apply-svg-color(pvar(--mainColor))
175 }
176}
177
141@mixin tertiary-button { 178@mixin tertiary-button {
142 @include button-focus($grey-button-outline-color); 179 @include button-focus($grey-button-outline-color);
143 180
@@ -509,6 +546,13 @@
509 min-height: $size; 546 min-height: $size;
510} 547}
511 548
549@mixin channel-avatar ($size) {
550 width: $size;
551 height: $size;
552 min-width: $size;
553 min-height: $size;
554}
555
512@mixin chevron ($size, $border-width) { 556@mixin chevron ($size, $border-width) {
513 border-style: solid; 557 border-style: solid;
514 border-width: $border-width $border-width 0 0; 558 border-width: $border-width $border-width 0 0;
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index c8316473d..bcd28215b 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -16,9 +16,10 @@ $grey-foreground-hover-color: #303030;
16$grey-button-outline-color: scale-color($grey-foreground-color, $alpha: -95%); 16$grey-button-outline-color: scale-color($grey-foreground-color, $alpha: -95%);
17 17
18$main-color: hsl(24, 90%, 50%); 18$main-color: hsl(24, 90%, 50%);
19$main-hover-color: lighten($main-color, 5%);
20$main-color-lighter: lighten($main-color, 10%); 19$main-color-lighter: lighten($main-color, 10%);
21$main-color-lightest: lighten($main-color, 40%); 20$main-color-lightest: lighten($main-color, 40%);
21$main-hover-color: lighten($main-color, 5%);
22
22$secondary-color: hsl(187, 77%, 34%); 23$secondary-color: hsl(187, 77%, 34%);
23 24
24$support-button: inherit; 25$support-button: inherit;
@@ -50,6 +51,8 @@ $menu-lateral-padding: 26px;
50$sub-menu-color: #F7F7F7; 51$sub-menu-color: #F7F7F7;
51$sub-menu-height: 81px; 52$sub-menu-height: 81px;
52 53
54$channel-background-color: #f6ede8;
55
53$footer-height: 30px; 56$footer-height: 30px;
54$footer-margin: 30px; 57$footer-margin: 30px;
55 58
@@ -98,7 +101,9 @@ $variables: (
98 101
99 --menuBackgroundColor: var(--menuBackgroundColor), 102 --menuBackgroundColor: var(--menuBackgroundColor),
100 --menuForegroundColor: var(--menuForegroundColor), 103 --menuForegroundColor: var(--menuForegroundColor),
104
101 --submenuColor: var(--submenuColor), 105 --submenuColor: var(--submenuColor),
106 --channelBackgroundColor: var(--channelBackgroundColor),
102 107
103 --inputForegroundColor: var(--inputForegroundColor), 108 --inputForegroundColor: var(--inputForegroundColor),
104 --inputBackgroundColor: var(--inputBackgroundColor), 109 --inputBackgroundColor: var(--inputBackgroundColor),