.actor-counters {
@include margin-left(15px);
+ @include actor-counters;
grid-row: 1;
grid-column: 3;
- color: pvar(--greyForegroundColor);
- font-size: 16px;
- display: flex;
- align-items: center;
- }
-
- .actor-counters > *:not(:last-child)::after {
- content: '•';
- margin: 0 10px;
- color: pvar(--mainColor);
}
.description-html {
my-video-miniature {
@include margin-right(15px);
+
min-width: $video-thumbnail-medium-width;
max-width: $video-thumbnail-medium-width;
}
h4 {
margin-bottom: 0;
}
+
+ .layout-row {
+ display: flex;
+ flex-direction: row;
+ }
+
+ .layout-column {
+ display: flex;
+ flex-direction: column;
+ }
}
}
const data = el.dataset as ChannelMiniatureMarkupData
const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent)
- this.dynamicElementService.setModel(component, { name: data.name })
+ const model = {
+ name: data.name,
+ displayLatestVideo: this.buildBoolean(data.displayLatestVideo) ?? true,
+ displayDescription: this.buildBoolean(data.displayDescription) ?? true
+ }
+
+ this.dynamicElementService.setModel(component, model)
return component
}
const data = el.dataset as ContainerMarkupData
const root = document.createElement('div')
- root.classList.add('peertube-container')
+
+ const layoutClass = data.layout
+ ? 'layout-' + data.layout
+ : 'layout-row'
+
+ root.classList.add('peertube-container', layoutClass)
if (data.width) {
root.setAttribute('width', data.width)
<div *ngIf="channel" class="channel">
- <my-actor-avatar [channel]="channel" size="34"></my-actor-avatar>
- <div class="display-name">{{ channel.displayName }}</div>
- <div class="username">{{ channel.name }}</div>
+ <div class="channel-avatar-row">
+ <my-actor-avatar [channel]="channel" [internalHref]="getVideoChannelLink()" i18n-title title="See this video channel"></my-actor-avatar>
- <div class="description">{{ channel.description }}</div>
+ <h6>
+ <a [routerLink]="getVideoChannelLink()" i18n-title title="See this video channel">
+ {{ channel.displayName }}
+ </a>
+ </h6>
+
+ <div class="actor-counters">
+ <div class="followers" i18n>{channel.followersCount, plural, =1 {1 subscriber} other {{{ channel.followersCount }} subscribers}}</div>
+
+ <span class="videos-count" *ngIf="totalVideos !== undefined" i18n>
+ {totalVideos, plural, =1 {1 videos} other {{{ totalVideos }} videos}}
+ </span>
+ </div>
+
+ <div *ngIf="displayDescription" class="description-html" [innerHTML]="descriptionHTML"></div>
+ </div>
+
+ <div class="video" *ngIf="video && displayLatestVideo">
+ <div i18n class="video-label">Latest published video</div>
+
+ <my-video-miniature-markup [uuid]="video.uuid" [onlyDisplayTitle]="true"></my-video-miniature-markup>
+ </div>
</div>
@import '_mixins';
.channel {
- border-radius: 15px;
- padding: 10px;
- width: min-content;
- border: 1px solid pvar(--mainColor);
+ padding: 20px;
+ background-color: pvar(--channelBackgroundColor);
+ margin: 0 30px 30px 0;
+ width: fit-content;
+}
+
+.channel-avatar-row,
+.video {
+ width: 280px;
+}
+
+.channel-avatar-row {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-template-rows: auto auto 1fr;
+ column-gap: 15px;
+
+ a {
+ @include peertube-word-wrap;
+
+ color: pvar(--mainForegroundColor);
+ }
+
+ my-actor-avatar {
+ @include actor-avatar-size(75px);
+
+ grid-column: 1;
+ grid-row: 1 / 4;
+ }
+
+ h6 {
+ grid-column: 2;
+ margin: 0;
+ }
+
+ .actor-counters {
+ @include actor-counters(5px);
+
+ font-size: 13px;
+ grid-column: 2;
+ }
+
+ .description-html {
+ @include fade-text(30px, pvar(--channelBackgroundColor));
+
+ max-height: 60px;
+ grid-column: 2;
+ }
+}
+
+.video-label {
+ font-size: 12px;
+ color: pvar(--greyForegroundColor);
+ margin: 15px 0 5px;
}
+import { map, switchMap } from 'rxjs/operators'
import { Component, Input, OnInit } from '@angular/core'
-import { VideoChannel, VideoChannelService } from '../../shared-main'
+import { MarkdownService, UserService } from '@app/core'
+import { Video, VideoSortField } from '@shared/models/videos'
+import { VideoChannel, VideoChannelService, VideoService } from '../../shared-main'
/*
* Markup component that creates a channel miniature only
})
export class ChannelMiniatureMarkupComponent implements OnInit {
@Input() name: string
+ @Input() displayLatestVideo: boolean
+ @Input() displayDescription: boolean
channel: VideoChannel
+ descriptionHTML: string
+ totalVideos: number
+ video: Video
constructor (
- private channelService: VideoChannelService
+ private markdown: MarkdownService,
+ private channelService: VideoChannelService,
+ private videoService: VideoService,
+ private userService: UserService
) { }
ngOnInit () {
this.channelService.getVideoChannel(this.name)
- .subscribe(channel => this.channel = channel)
+ .subscribe(async channel => {
+ this.channel = channel
+
+ this.descriptionHTML = await this.markdown.textMarkdownToHTML(channel.description)
+
+ this.loadVideos()
+ })
+ }
+
+ getVideoChannelLink () {
+ return [ '/c', this.channel.nameWithHost ]
+ }
+
+ private loadVideos () {
+ const videoOptions = {
+ videoChannel: this.channel,
+ videoPagination: {
+ currentPage: 1,
+ itemsPerPage: 1
+ },
+ sort: '-publishedAt' as VideoSortField,
+ count: 1
+ }
+
+ this.userService.getAnonymousOrLoggedUser()
+ .pipe(
+ map(user => user.nsfwPolicy),
+ switchMap(nsfwPolicy => this.videoService.getVideoChannelVideos({ ...videoOptions, nsfwPolicy }))
+ )
+ .subscribe(({ total, data }) => {
+ this.totalVideos = total
+ this.video = data[0]
+ })
}
}
min-height: $size;
}
+@mixin actor-counters ($separator-margin: 10px) {
+ color: pvar(--greyForegroundColor);
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+
+ > *:not(:last-child)::after {
+ content: '•';
+ margin: 0 $separator-margin;
+ color: pvar(--mainColor);
+ }
+}
+
@mixin chevron ($size, $border-width) {
border-style: solid;
border-width: $border-width $border-width 0 0;
export type ChannelMiniatureMarkupData = {
// Channel name (username)
name: string
+
+ displayLatestVideo?: string // boolean
+ displayDescription?: string // boolean
}
export type VideosListMarkupData = {
width?: string
title?: string
description?: string
+ layout?: 'row' | 'column'
}