mirror of https://github.com/Chocobozzz/PeerTube
Redesign account page
parent
60c35932f6
commit
67264e060b
|
@ -1,15 +0,0 @@
|
|||
<h1 class="sr-only" i18n>About</h1>
|
||||
<div class="margin-content">
|
||||
<div *ngIf="account" class="row no-gutters">
|
||||
<div class="block col-md-6 col-sm-12 pr-2">
|
||||
<h2 i18n class="small-title">DESCRIPTION</h2>
|
||||
<div class="content" [innerHtml]="getAccountDescription()"></div>
|
||||
</div>
|
||||
|
||||
<div class="block col-md-6 col-sm-12">
|
||||
<h2 i18n class="small-title">STATS</h2>
|
||||
|
||||
<div i18n class="content">Joined {{ account.createdAt | date }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
.block {
|
||||
margin-bottom: 40px;
|
||||
|
||||
.small-title {
|
||||
@include in-content-small-title;
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import { Subscription } from 'rxjs'
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { MarkdownService } from '@app/core'
|
||||
import { Account, AccountService } from '@app/shared/shared-main'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-about',
|
||||
templateUrl: './account-about.component.html',
|
||||
styleUrls: [ './account-about.component.scss' ]
|
||||
})
|
||||
export class AccountAboutComponent implements OnInit, OnDestroy {
|
||||
account: Account
|
||||
descriptionHTML = ''
|
||||
|
||||
private accountSub: Subscription
|
||||
|
||||
constructor (
|
||||
private accountService: AccountService,
|
||||
private markdownService: MarkdownService
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
// Parent get the account for us
|
||||
this.accountSub = this.accountService.accountLoaded
|
||||
.subscribe(async account => {
|
||||
this.account = account
|
||||
this.descriptionHTML = await this.markdownService.textMarkdownToHTML(this.account.description, true)
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
if (this.accountSub) this.accountSub.unsubscribe()
|
||||
}
|
||||
|
||||
getAccountDescription () {
|
||||
if (this.descriptionHTML) return this.descriptionHTML
|
||||
|
||||
return $localize`No description`
|
||||
}
|
||||
}
|
|
@ -64,9 +64,14 @@ export class AccountSearchComponent extends AbstractVideoList implements OnInit,
|
|||
}
|
||||
|
||||
updateSearch (value: string) {
|
||||
if (value === '') this.router.navigate(['../videos'], { relativeTo: this.route })
|
||||
this.search = value
|
||||
|
||||
if (!this.search) {
|
||||
this.router.navigate([ '../videos' ], { relativeTo: this.route })
|
||||
return
|
||||
}
|
||||
|
||||
this.videos = []
|
||||
this.reloadVideos()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { MetaGuard } from '@ngx-meta/core'
|
||||
import { AccountsComponent } from './accounts.component'
|
||||
import { AccountVideosComponent } from './account-videos/account-videos.component'
|
||||
import { AccountAboutComponent } from './account-about/account-about.component'
|
||||
import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component'
|
||||
import { AccountSearchComponent } from './account-search/account-search.component'
|
||||
import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component'
|
||||
import { AccountVideosComponent } from './account-videos/account-videos.component'
|
||||
import { AccountsComponent } from './accounts.component'
|
||||
|
||||
const accountsRoutes: Routes = [
|
||||
{
|
||||
|
@ -31,15 +30,6 @@ const accountsRoutes: Routes = [
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AccountAboutComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`About account`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'videos',
|
||||
component: AccountVideosComponent,
|
||||
|
|
|
@ -1,57 +1,89 @@
|
|||
<div *ngIf="account" class="row">
|
||||
<div class="sub-menu">
|
||||
<div *ngIf="account" class="root">
|
||||
<div class="account-info">
|
||||
|
||||
<div class="actor">
|
||||
<img [src]="account.avatarUrl" alt="Avatar" />
|
||||
<div class="account-avatar-row">
|
||||
<img class="account-avatar" [src]="account.avatarUrl" alt="Avatar" />
|
||||
|
||||
<div class="actor-info">
|
||||
<div class="actor-names">
|
||||
<div class="actor-display-name">{{ account.displayName }}</div>
|
||||
<div class="actor-name">
|
||||
<span>{{ account.nameWithHost }}</span>
|
||||
<button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()"
|
||||
class="btn btn-outline-secondary btn-sm copy-button"
|
||||
>
|
||||
<span class="glyphicon glyphicon-duplicate"></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="section-label" i18n>PEERTUBE ACCOUNT</div>
|
||||
|
||||
<div class="actor-info">
|
||||
<div>
|
||||
<div class="actor-display-name">
|
||||
<h1>{{ account.displayName }}</h1>
|
||||
|
||||
<my-user-moderation-dropdown
|
||||
[prependActions]="prependModerationActions"
|
||||
buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto"
|
||||
(userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
|
||||
></my-user-moderation-dropdown>
|
||||
|
||||
<span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span>
|
||||
<span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span>
|
||||
<span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span>
|
||||
<span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span>
|
||||
<span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
|
||||
</div>
|
||||
|
||||
<div class="actor-handle">
|
||||
<span>@{{ account.nameWithHost }}</span>
|
||||
<button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()"
|
||||
class="btn btn-outline-secondary btn-sm copy-button" title="Copy account handle" i18n-title
|
||||
>
|
||||
<span class="glyphicon glyphicon-duplicate"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="actor-counters">
|
||||
<span i18n>{naiveAggregatedSubscribers(), plural, =1 {1 subscriber} other {{{ naiveAggregatedSubscribers() }} subscribers}}</span>
|
||||
|
||||
<span class="videos-count" *ngIf="accountVideosCount !== undefined" i18n>
|
||||
{accountVideosCount, plural, =1 {1 videos} other {{{ accountVideosCount }} videos}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span>
|
||||
<span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span>
|
||||
<span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span>
|
||||
<span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span>
|
||||
<span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
|
||||
|
||||
<my-user-moderation-dropdown
|
||||
[prependActions]="prependModerationActions"
|
||||
buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto"
|
||||
(userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
|
||||
></my-user-moderation-dropdown>
|
||||
</div>
|
||||
<div class="actor-followers" [title]="accountFollowerTitle">
|
||||
{{ subscribersDisplayFor(naiveAggregatedSubscribers) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a *ngIf="isAccountManageable && !isInSmallView" routerLink="/my-account" class="btn btn-outline-tertiary mr-2" i18n>Manage account</a>
|
||||
<my-subscribe-button *ngIf="videoChannels" [account]="account" [videoChannels]="videoChannels"></my-subscribe-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="links w-100">
|
||||
<ng-template #linkTemplate let-item="item">
|
||||
<a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
|
||||
</ng-template>
|
||||
<div class="description" [ngClass]="{ expanded: accountDescriptionExpanded }">
|
||||
<div class="description-html" [innerHTML]="accountDescriptionHTML"></div>
|
||||
|
||||
<list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow>
|
||||
<div class="created-at" i18n>Account created on {{ account.createdAt | date }}</div>
|
||||
</div>
|
||||
|
||||
<simple-search-input (searchChanged)="searchChanged($event)" name="search-videos" i18n-placeholder placeholder="Search videos"></simple-search-input>
|
||||
<div *ngIf="!accountDescriptionExpanded" class="show-more" role="button"
|
||||
(click)="accountDescriptionExpanded = !accountDescriptionExpanded"
|
||||
title="Show the complete description" i18n-title i18n
|
||||
>
|
||||
Show more...
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<a *ngIf="isManageable() && !isInSmallView()" routerLink="/my-account" class="peertube-button-link orange-button" i18n>
|
||||
Manage account
|
||||
</a>
|
||||
|
||||
<my-subscribe-button *ngIf="videoChannels" [account]="account" [videoChannels]="videoChannels"></my-subscribe-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="margin-content">
|
||||
<router-outlet (activate)="onOutletLoaded($event)"></router-outlet>
|
||||
<div class="links">
|
||||
<ng-template #linkTemplate let-item="item">
|
||||
<a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
|
||||
</ng-template>
|
||||
|
||||
<list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></list-overflow>
|
||||
|
||||
<simple-search-input
|
||||
[alwaysShow]="!isInSmallView()" (searchChanged)="searchChanged($event)"
|
||||
(inputDisplayChanged)="onSearchInputDisplayChanged($event)" name="search-videos"
|
||||
i18n-iconTitle icon-title="Search account videos"
|
||||
i18n-placeholder placeholder="Search account videos"
|
||||
></simple-search-input>
|
||||
</div>
|
||||
|
||||
<router-outlet (activate)="onOutletLoaded($event)"></router-outlet>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="prependModerationActions">
|
||||
|
|
|
@ -1,49 +1,26 @@
|
|||
// Bootstrap grid utilities require functions, variables and mixins
|
||||
@import 'node_modules/bootstrap/scss/functions';
|
||||
@import 'node_modules/bootstrap/scss/variables';
|
||||
@import 'node_modules/bootstrap/scss/mixins';
|
||||
@import 'node_modules/bootstrap/scss/grid';
|
||||
|
||||
@import '_variables';
|
||||
@import '_mixins';
|
||||
@import '_actor';
|
||||
@import '_miniature';
|
||||
|
||||
.sub-menu {
|
||||
@include sub-menu-with-actor;
|
||||
|
||||
.actor {
|
||||
width: 100%;
|
||||
}
|
||||
.root {
|
||||
--myGlobalPadding: 60px;
|
||||
--myImgMargin: 30px;
|
||||
--myFontSize: 16px;
|
||||
--myGreyFontSize: 16px;
|
||||
}
|
||||
|
||||
.margin-content {
|
||||
// margin-content is required, but child views have their own margins
|
||||
// that match views outside the scope of accounts, so we only align
|
||||
// them with the margins of .sub-menu when required.
|
||||
margin: 0;
|
||||
.section-label {
|
||||
@include section-label-responsive;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
.links {
|
||||
@include fluid-videos-miniature-layout;
|
||||
|
||||
display: flex;
|
||||
height: max-content;
|
||||
margin-left: auto;
|
||||
margin-top: 10px;
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
flex-flow: column-reverse;
|
||||
|
||||
a {
|
||||
margin-top: 0.25rem;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include peertube-button-outline;
|
||||
}
|
||||
|
||||
my-subscribe-button {
|
||||
min-height: 30px;
|
||||
}
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
my-user-moderation-dropdown,
|
||||
|
@ -60,39 +37,98 @@ my-user-moderation-dropdown,
|
|||
|
||||
.copy-button {
|
||||
border: none;
|
||||
padding: 5px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content;
|
||||
grid-template-rows: auto auto;
|
||||
|
||||
background-color: pvar(--submenuColor);
|
||||
margin-bottom: 45px;
|
||||
padding: var(--myGlobalPadding) var(--myGlobalPadding) 0 var(--myGlobalPadding);
|
||||
font-size: var(--myFontSize);
|
||||
}
|
||||
|
||||
.account-avatar-row {
|
||||
@include avatar-row-responsive(var(--myImgMargin), var(--myGreyFontSize));
|
||||
}
|
||||
|
||||
.description {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
.created-at {
|
||||
margin-top: 15px;
|
||||
color: pvar(--greyForegroundColor);
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.show-more {
|
||||
@include show-more-description;
|
||||
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-content: flex-start;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-view) {
|
||||
.root {
|
||||
--myGlobalPadding: 45px;
|
||||
--myChannelImgMargin: 15px;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
display: block;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.description:not(.expanded) {
|
||||
max-height: 70px;
|
||||
|
||||
@include fade-text(30px, pvar(--submenuColor));
|
||||
}
|
||||
|
||||
.show-more {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
.sub-menu {
|
||||
.actor {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.root {
|
||||
--myGlobalPadding: 15px;
|
||||
--myFontSize: 14px;
|
||||
--myGreyFontSize: 13px;
|
||||
}
|
||||
|
||||
img,
|
||||
.actor-info .actor-names .actor-display-name {
|
||||
margin-right: 0;
|
||||
}
|
||||
.account-info {
|
||||
display: block;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.actor-info {
|
||||
.actor-names {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.links {
|
||||
margin: auto !important;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
my-user-moderation-dropdown {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.actor-followers {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.show-more {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,19 @@ import { Subscription } from 'rxjs'
|
|||
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { AuthService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core'
|
||||
import { Account, AccountService, DropdownAction, ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main'
|
||||
import { AuthService, MarkdownService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core'
|
||||
import {
|
||||
Account,
|
||||
AccountService,
|
||||
DropdownAction,
|
||||
ListOverflowItem,
|
||||
VideoChannel,
|
||||
VideoChannelService,
|
||||
VideoService
|
||||
} from '@app/shared/shared-main'
|
||||
import { AccountReportComponent } from '@app/shared/shared-moderation'
|
||||
import { User, UserRight } from '@shared/models'
|
||||
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
|
||||
import { User, UserRight } from '@shared/models'
|
||||
import { AccountSearchComponent } from './account-search/account-search.component'
|
||||
|
||||
@Component({
|
||||
|
@ -15,16 +23,23 @@ import { AccountSearchComponent } from './account-search/account-search.componen
|
|||
})
|
||||
export class AccountsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('accountReportModal') accountReportModal: AccountReportComponent
|
||||
|
||||
accountSearch: AccountSearchComponent
|
||||
|
||||
account: Account
|
||||
accountUser: User
|
||||
videoChannels: VideoChannel[] = []
|
||||
links: ListOverflowItem[] = []
|
||||
|
||||
isAccountManageable = false
|
||||
videoChannels: VideoChannel[] = []
|
||||
|
||||
links: ListOverflowItem[] = []
|
||||
hideMenu = false
|
||||
|
||||
accountFollowerTitle = ''
|
||||
|
||||
accountVideosCount: number
|
||||
accountDescriptionHTML = ''
|
||||
accountDescriptionExpanded = false
|
||||
|
||||
prependModerationActions: DropdownAction<any>[]
|
||||
|
||||
private routeSub: Subscription
|
||||
|
@ -38,6 +53,8 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
|||
private restExtractor: RestExtractor,
|
||||
private redirectService: RedirectService,
|
||||
private authService: AuthService,
|
||||
private videoService: VideoService,
|
||||
private markdown: MarkdownService,
|
||||
private screenService: ScreenService
|
||||
) {
|
||||
}
|
||||
|
@ -63,8 +80,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.links = [
|
||||
{ label: $localize`VIDEO CHANNELS`, routerLink: 'video-channels' },
|
||||
{ label: $localize`VIDEOS`, routerLink: 'videos' },
|
||||
{ label: $localize`ABOUT`, routerLink: 'about' }
|
||||
{ label: $localize`VIDEOS`, routerLink: 'videos' }
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -72,19 +88,29 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
|||
if (this.routeSub) this.routeSub.unsubscribe()
|
||||
}
|
||||
|
||||
get naiveAggregatedSubscribers () {
|
||||
naiveAggregatedSubscribers () {
|
||||
return this.videoChannels.reduce(
|
||||
(acc, val) => acc + val.followersCount,
|
||||
this.account.followersCount // accumulator starts with the base number of subscribers the account has
|
||||
)
|
||||
}
|
||||
|
||||
get isInSmallView () {
|
||||
isUserLoggedIn () {
|
||||
return this.authService.isLoggedIn()
|
||||
}
|
||||
|
||||
isInSmallView () {
|
||||
return this.screenService.isInSmallView()
|
||||
}
|
||||
|
||||
isManageable () {
|
||||
if (!this.isUserLoggedIn()) return false
|
||||
|
||||
return this.account?.userId === this.authService.getUser().id
|
||||
}
|
||||
|
||||
onUserChanged () {
|
||||
this.getUserIfNeeded(this.account)
|
||||
this.loadUserIfNeeded(this.account)
|
||||
}
|
||||
|
||||
onUserDeleted () {
|
||||
|
@ -113,40 +139,30 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
|||
if (this.accountSearch) this.accountSearch.updateSearch(search)
|
||||
}
|
||||
|
||||
private onAccount (account: Account) {
|
||||
onSearchInputDisplayChanged (displayed: boolean) {
|
||||
this.hideMenu = this.isInSmallView() && displayed
|
||||
}
|
||||
|
||||
private async onAccount (account: Account) {
|
||||
this.accountFollowerTitle = $localize`${account.followersCount} direct account followers`
|
||||
|
||||
this.prependModerationActions = undefined
|
||||
|
||||
this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description)
|
||||
|
||||
// After the markdown renderer to avoid layout changes
|
||||
this.account = account
|
||||
|
||||
if (this.authService.isLoggedIn()) {
|
||||
this.authService.userInformationLoaded.subscribe(
|
||||
() => {
|
||||
this.isAccountManageable = this.account.userId && this.account.userId === this.authService.getUser().id
|
||||
|
||||
const followers = this.subscribersDisplayFor(account.followersCount)
|
||||
this.accountFollowerTitle = $localize`${followers} direct account followers`
|
||||
|
||||
// It's not our account, we can report it
|
||||
if (!this.isAccountManageable) {
|
||||
this.prependModerationActions = [
|
||||
{
|
||||
label: $localize`Report this account`,
|
||||
handler: () => this.showReportModal()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
this.getUserIfNeeded(account)
|
||||
this.updateModerationActions()
|
||||
this.loadUserIfNeeded(account)
|
||||
this.loadAccountVideosCount()
|
||||
}
|
||||
|
||||
private showReportModal () {
|
||||
this.accountReportModal.show()
|
||||
}
|
||||
|
||||
private getUserIfNeeded (account: Account) {
|
||||
private loadUserIfNeeded (account: Account) {
|
||||
if (!account.userId || !this.authService.isLoggedIn()) return
|
||||
|
||||
const user = this.authService.getUser()
|
||||
|
@ -158,4 +174,33 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private updateModerationActions () {
|
||||
if (!this.authService.isLoggedIn()) return
|
||||
|
||||
this.authService.userInformationLoaded.subscribe(
|
||||
() => {
|
||||
if (this.isManageable()) return
|
||||
|
||||
// It's not our account, we can report it
|
||||
this.prependModerationActions = [
|
||||
{
|
||||
label: $localize`Report this account`,
|
||||
handler: () => this.showReportModal()
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private loadAccountVideosCount () {
|
||||
this.videoService.getAccountVideos({
|
||||
account: this.account,
|
||||
videoPagination: {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 0
|
||||
},
|
||||
sort: '-publishedAt'
|
||||
}).subscribe(res => this.accountVideosCount = res.total)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,9 @@ import { SharedMainModule } from '@app/shared/shared-main'
|
|||
import { SharedModerationModule } from '@app/shared/shared-moderation'
|
||||
import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription'
|
||||
import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
|
||||
import { AccountAboutComponent } from './account-about/account-about.component'
|
||||
import { AccountSearchComponent } from './account-search/account-search.component'
|
||||
import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component'
|
||||
import { AccountVideosComponent } from './account-videos/account-videos.component'
|
||||
import { AccountSearchComponent } from './account-search/account-search.component'
|
||||
import { AccountsRoutingModule } from './accounts-routing.module'
|
||||
import { AccountsComponent } from './accounts.component'
|
||||
|
||||
|
@ -28,7 +27,6 @@ import { AccountsComponent } from './accounts.component'
|
|||
AccountsComponent,
|
||||
AccountVideosComponent,
|
||||
AccountVideoChannelsComponent,
|
||||
AccountAboutComponent,
|
||||
AccountSearchComponent
|
||||
],
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<ng-template #ownerTemplate>
|
||||
<div class="owner-block">
|
||||
<div class="avatar-row">
|
||||
<img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" />
|
||||
<img class="channel-avatar" [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" />
|
||||
|
||||
<div class="actor-info">
|
||||
<h4>{{ videoChannel.ownerAccount.displayName }}</h4>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
@import '_actor';
|
||||
@import '_miniature';
|
||||
|
||||
.root {
|
||||
|
@ -11,11 +12,7 @@
|
|||
}
|
||||
|
||||
.section-label {
|
||||
color: pvar(--mainColor);
|
||||
font-size: 12px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: $font-bold;
|
||||
letter-spacing: 2.5px;
|
||||
@include section-label-responsive;
|
||||
}
|
||||
|
||||
.links {
|
||||
|
@ -34,48 +31,7 @@
|
|||
}
|
||||
|
||||
.channel-avatar-row {
|
||||
display: flex;
|
||||
grid-column: 1;
|
||||
margin-bottom: 30px;
|
||||
|
||||
img {
|
||||
@include channel-avatar(120px);
|
||||
}
|
||||
|
||||
> div {
|
||||
margin-left: var(--myChannelImgMargin);
|
||||
}
|
||||
|
||||
.actor-info {
|
||||
display: flex;
|
||||
|
||||
> div:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.actor-display-name {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: $font-bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actor-handle,
|
||||
.actor-counters {
|
||||
color: pvar(--greyForegroundColor);
|
||||
font-size: var(--myGreyChannelFontSize);
|
||||
}
|
||||
|
||||
.actor-counters > *:not(:last-child)::after {
|
||||
content: '•';
|
||||
margin: 0 10px;
|
||||
color: pvar(--mainColor);
|
||||
}
|
||||
@include avatar-row-responsive(var(--myChannelImgMargin), var(--myGreyChannelFontSize));
|
||||
}
|
||||
|
||||
.channel-description {
|
||||
|
@ -83,12 +39,10 @@
|
|||
}
|
||||
|
||||
.show-more {
|
||||
display: none;
|
||||
color: pvar(--mainColor);
|
||||
cursor: pointer;
|
||||
margin: 10px auto 45px auto;
|
||||
}
|
||||
@include show-more-description;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.channel-buttons {
|
||||
display: flex;
|
||||
|
@ -280,24 +234,6 @@
|
|||
width: min-content;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 10px;
|
||||
letter-spacing: 2.1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.channel-avatar-row {
|
||||
margin-bottom: 15px;
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
img {
|
||||
@include channel-avatar(80px);
|
||||
}
|
||||
}
|
||||
|
||||
.show-more {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
|
|||
isManageable () {
|
||||
if (!this.isUserLoggedIn()) return false
|
||||
|
||||
return this.videoChannel.ownerAccount.userId === this.authService.getUser().id
|
||||
return this.videoChannel?.ownerAccount.userId === this.authService.getUser().id
|
||||
}
|
||||
|
||||
activateCopiedMessage () {
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<span>
|
||||
<my-global-icon iconName="search" aria-label="Search" role="button" (click)="showInput()"></my-global-icon>
|
||||
|
||||
<div class="root">
|
||||
<input
|
||||
#ref
|
||||
type="text"
|
||||
[(ngModel)]="value"
|
||||
(focusout)="focusLost()"
|
||||
(keyup.enter)="searchChange()"
|
||||
[hidden]="!shown"
|
||||
[hidden]="!inputShown"
|
||||
[name]="name"
|
||||
[placeholder]="placeholder"
|
||||
>
|
||||
</span>
|
||||
|
||||
<my-global-icon iconName="search" aria-label="Search" role="button" (click)="onIconClick()" [title]="iconTitle"></my-global-icon>
|
||||
|
||||
<my-global-icon *ngIf="!alwaysShow && inputShown" i18n-title title="Close search" iconName="cross" (click)="hideInput()"></my-global-icon>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
span {
|
||||
opacity: .6;
|
||||
|
||||
&:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
.root {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
my-global-icon {
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: pvar(--mainHoverColor);
|
||||
}
|
||||
|
||||
&[iconName=search] {
|
||||
color: pvar(--mainColor);
|
||||
}
|
||||
|
||||
&[iconName=cross] {
|
||||
color: pvar(--mainForegroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
@include peertube-input-text(150px);
|
||||
|
||||
height: 22px; // maximum height for the account/video-channels links
|
||||
padding-left: 10px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
&::placeholder {
|
||||
font-size: 15px;
|
||||
}
|
||||
@include peertube-input-text(200px);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Subject } from 'rxjs'
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
|
||||
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'simple-search-input',
|
||||
|
@ -13,11 +13,14 @@ export class SimpleSearchInputComponent implements OnInit {
|
|||
|
||||
@Input() name = 'search'
|
||||
@Input() placeholder = $localize`Search`
|
||||
@Input() iconTitle = $localize`Search`
|
||||
@Input() alwaysShow = true
|
||||
|
||||
@Output() searchChanged = new EventEmitter<string>()
|
||||
@Output() inputDisplayChanged = new EventEmitter<boolean>()
|
||||
|
||||
value = ''
|
||||
shown: boolean
|
||||
inputShown: boolean
|
||||
|
||||
private searchSubject = new Subject<string>()
|
||||
|
||||
|
@ -35,20 +38,51 @@ export class SimpleSearchInputComponent implements OnInit {
|
|||
.subscribe(value => this.searchChanged.emit(value))
|
||||
|
||||
this.searchSubject.next(this.value)
|
||||
|
||||
if (this.isInputShown()) this.showInput(false)
|
||||
}
|
||||
|
||||
showInput () {
|
||||
this.shown = true
|
||||
setTimeout(() => this.input.nativeElement.focus())
|
||||
isInputShown () {
|
||||
if (this.alwaysShow) return true
|
||||
|
||||
return this.inputShown
|
||||
}
|
||||
|
||||
onIconClick () {
|
||||
if (!this.isInputShown()) {
|
||||
this.showInput()
|
||||
return
|
||||
}
|
||||
|
||||
this.searchChange()
|
||||
}
|
||||
|
||||
showInput (focus = true) {
|
||||
this.inputShown = true
|
||||
this.inputDisplayChanged.emit(this.inputShown)
|
||||
|
||||
if (focus) {
|
||||
setTimeout(() => this.input.nativeElement.focus())
|
||||
}
|
||||
}
|
||||
|
||||
hideInput () {
|
||||
this.inputShown = false
|
||||
|
||||
if (this.isInputShown() === false) {
|
||||
this.inputDisplayChanged.emit(this.inputShown)
|
||||
}
|
||||
}
|
||||
|
||||
focusLost () {
|
||||
if (this.value !== '') return
|
||||
this.shown = false
|
||||
if (this.value) return
|
||||
|
||||
this.hideInput()
|
||||
}
|
||||
|
||||
searchChange () {
|
||||
this.router.navigate(['./search'], { relativeTo: this.route })
|
||||
this.router.navigate([ './search' ], { relativeTo: this.route })
|
||||
|
||||
this.searchSubject.next(this.value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
@import '_variables';
|
||||
|
||||
@mixin section-label-responsive {
|
||||
color: pvar(--mainColor);
|
||||
font-size: 12px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: $font-bold;
|
||||
letter-spacing: 2.5px;
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
font-size: 10px;
|
||||
letter-spacing: 2.1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin show-more-description {
|
||||
color: pvar(--mainColor);
|
||||
cursor: pointer;
|
||||
margin: 10px auto 45px auto;
|
||||
}
|
||||
|
||||
@mixin avatar-row-responsive ($img-margin, $grey-font-size) {
|
||||
display: flex;
|
||||
grid-column: 1;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.channel-avatar {
|
||||
@include channel-avatar(120px);
|
||||
}
|
||||
|
||||
.account-avatar {
|
||||
@include avatar(120px);
|
||||
}
|
||||
|
||||
> div {
|
||||
margin-left: $img-margin;
|
||||
}
|
||||
|
||||
.actor-info {
|
||||
display: flex;
|
||||
|
||||
> div:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.actor-display-name {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: $font-bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actor-handle,
|
||||
.actor-counters {
|
||||
color: pvar(--greyForegroundColor);
|
||||
font-size: $grey-font-size;
|
||||
}
|
||||
|
||||
.actor-counters > *:not(:last-child)::after {
|
||||
content: '•';
|
||||
margin: 0 10px;
|
||||
color: pvar(--mainColor);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
margin-bottom: 15px;
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.channel-avatar {
|
||||
@include channel-avatar(80px);
|
||||
}
|
||||
|
||||
.account-avatar {
|
||||
@include avatar(120px);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue