diff options
author | Chocobozzz <me@florianbigard.com> | 2021-03-26 13:20:37 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2021-03-31 09:05:51 +0200 |
commit | 67264e060b6068399dae9a67abae035a73b84af1 (patch) | |
tree | dabb735a530c9389c941f7ff1d44fa4b5f6db03e /client/src/app/+accounts | |
parent | 60c35932f6a14cfe83bb0e54407427cce70171ea (diff) | |
download | PeerTube-67264e060b6068399dae9a67abae035a73b84af1.tar.gz PeerTube-67264e060b6068399dae9a67abae035a73b84af1.tar.zst PeerTube-67264e060b6068399dae9a67abae035a73b84af1.zip |
Redesign account page
Diffstat (limited to 'client/src/app/+accounts')
9 files changed, 268 insertions, 229 deletions
diff --git a/client/src/app/+accounts/account-about/account-about.component.html b/client/src/app/+accounts/account-about/account-about.component.html deleted file mode 100644 index e9e0e4079..000000000 --- a/client/src/app/+accounts/account-about/account-about.component.html +++ /dev/null | |||
@@ -1,15 +0,0 @@ | |||
1 | <h1 class="sr-only" i18n>About</h1> | ||
2 | <div class="margin-content"> | ||
3 | <div *ngIf="account" class="row no-gutters"> | ||
4 | <div class="block col-md-6 col-sm-12 pr-2"> | ||
5 | <h2 i18n class="small-title">DESCRIPTION</h2> | ||
6 | <div class="content" [innerHtml]="getAccountDescription()"></div> | ||
7 | </div> | ||
8 | |||
9 | <div class="block col-md-6 col-sm-12"> | ||
10 | <h2 i18n class="small-title">STATS</h2> | ||
11 | |||
12 | <div i18n class="content">Joined {{ account.createdAt | date }}</div> | ||
13 | </div> | ||
14 | </div> | ||
15 | </div> | ||
diff --git a/client/src/app/+accounts/account-about/account-about.component.scss b/client/src/app/+accounts/account-about/account-about.component.scss deleted file mode 100644 index 5bcd4b561..000000000 --- a/client/src/app/+accounts/account-about/account-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/+accounts/account-about/account-about.component.ts b/client/src/app/+accounts/account-about/account-about.component.ts deleted file mode 100644 index 6cf846d72..000000000 --- a/client/src/app/+accounts/account-about/account-about.component.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { Subscription } from 'rxjs' | ||
2 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
3 | import { MarkdownService } from '@app/core' | ||
4 | import { Account, AccountService } from '@app/shared/shared-main' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-account-about', | ||
8 | templateUrl: './account-about.component.html', | ||
9 | styleUrls: [ './account-about.component.scss' ] | ||
10 | }) | ||
11 | export class AccountAboutComponent implements OnInit, OnDestroy { | ||
12 | account: Account | ||
13 | descriptionHTML = '' | ||
14 | |||
15 | private accountSub: Subscription | ||
16 | |||
17 | constructor ( | ||
18 | private accountService: AccountService, | ||
19 | private markdownService: MarkdownService | ||
20 | ) { } | ||
21 | |||
22 | ngOnInit () { | ||
23 | // Parent get the account for us | ||
24 | this.accountSub = this.accountService.accountLoaded | ||
25 | .subscribe(async account => { | ||
26 | this.account = account | ||
27 | this.descriptionHTML = await this.markdownService.textMarkdownToHTML(this.account.description, true) | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | ngOnDestroy () { | ||
32 | if (this.accountSub) this.accountSub.unsubscribe() | ||
33 | } | ||
34 | |||
35 | getAccountDescription () { | ||
36 | if (this.descriptionHTML) return this.descriptionHTML | ||
37 | |||
38 | return $localize`No description` | ||
39 | } | ||
40 | } | ||
diff --git a/client/src/app/+accounts/account-search/account-search.component.ts b/client/src/app/+accounts/account-search/account-search.component.ts index dda4bf0c7..f54ab846a 100644 --- a/client/src/app/+accounts/account-search/account-search.component.ts +++ b/client/src/app/+accounts/account-search/account-search.component.ts | |||
@@ -64,9 +64,14 @@ export class AccountSearchComponent extends AbstractVideoList implements OnInit, | |||
64 | } | 64 | } |
65 | 65 | ||
66 | updateSearch (value: string) { | 66 | updateSearch (value: string) { |
67 | if (value === '') this.router.navigate(['../videos'], { relativeTo: this.route }) | ||
68 | this.search = value | 67 | this.search = value |
69 | 68 | ||
69 | if (!this.search) { | ||
70 | this.router.navigate([ '../videos' ], { relativeTo: this.route }) | ||
71 | return | ||
72 | } | ||
73 | |||
74 | this.videos = [] | ||
70 | this.reloadVideos() | 75 | this.reloadVideos() |
71 | } | 76 | } |
72 | 77 | ||
diff --git a/client/src/app/+accounts/accounts-routing.module.ts b/client/src/app/+accounts/accounts-routing.module.ts index 15937a67b..3bf0f7185 100644 --- a/client/src/app/+accounts/accounts-routing.module.ts +++ b/client/src/app/+accounts/accounts-routing.module.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { RouterModule, Routes } from '@angular/router' | 2 | import { RouterModule, Routes } from '@angular/router' |
3 | import { MetaGuard } from '@ngx-meta/core' | 3 | import { MetaGuard } from '@ngx-meta/core' |
4 | import { AccountsComponent } from './accounts.component' | ||
5 | import { AccountVideosComponent } from './account-videos/account-videos.component' | ||
6 | import { AccountAboutComponent } from './account-about/account-about.component' | ||
7 | import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component' | ||
8 | import { AccountSearchComponent } from './account-search/account-search.component' | 4 | import { AccountSearchComponent } from './account-search/account-search.component' |
5 | import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component' | ||
6 | import { AccountVideosComponent } from './account-videos/account-videos.component' | ||
7 | import { AccountsComponent } from './accounts.component' | ||
9 | 8 | ||
10 | const accountsRoutes: Routes = [ | 9 | const accountsRoutes: Routes = [ |
11 | { | 10 | { |
@@ -32,15 +31,6 @@ const accountsRoutes: Routes = [ | |||
32 | } | 31 | } |
33 | }, | 32 | }, |
34 | { | 33 | { |
35 | path: 'about', | ||
36 | component: AccountAboutComponent, | ||
37 | data: { | ||
38 | meta: { | ||
39 | title: $localize`About account` | ||
40 | } | ||
41 | } | ||
42 | }, | ||
43 | { | ||
44 | path: 'videos', | 34 | path: 'videos', |
45 | component: AccountVideosComponent, | 35 | component: AccountVideosComponent, |
46 | data: { | 36 | data: { |
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 1903bb36f..92d24ce94 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -1,57 +1,89 @@ | |||
1 | <div *ngIf="account" class="row"> | 1 | <div *ngIf="account" class="root"> |
2 | <div class="sub-menu"> | 2 | <div class="account-info"> |
3 | 3 | ||
4 | <div class="actor"> | 4 | <div class="account-avatar-row"> |
5 | <img [src]="account.avatarUrl" alt="Avatar" /> | 5 | <img class="account-avatar" [src]="account.avatarUrl" alt="Avatar" /> |
6 | 6 | ||
7 | <div class="actor-info"> | 7 | <div> |
8 | <div class="actor-names"> | 8 | <div class="section-label" i18n>PEERTUBE ACCOUNT</div> |
9 | <div class="actor-display-name">{{ account.displayName }}</div> | 9 | |
10 | <div class="actor-name"> | 10 | <div class="actor-info"> |
11 | <span>{{ account.nameWithHost }}</span> | 11 | <div> |
12 | <button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()" | 12 | <div class="actor-display-name"> |
13 | class="btn btn-outline-secondary btn-sm copy-button" | 13 | <h1>{{ account.displayName }}</h1> |
14 | > | 14 | |
15 | <span class="glyphicon glyphicon-duplicate"></span> | 15 | <my-user-moderation-dropdown |
16 | </button> | 16 | [prependActions]="prependModerationActions" |
17 | buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto" | ||
18 | (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" | ||
19 | ></my-user-moderation-dropdown> | ||
20 | |||
21 | <span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span> | ||
22 | <span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span> | ||
23 | <span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span> | ||
24 | <span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span> | ||
25 | <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> | ||
26 | </div> | ||
27 | |||
28 | <div class="actor-handle"> | ||
29 | <span>@{{ account.nameWithHost }}</span> | ||
30 | <button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()" | ||
31 | class="btn btn-outline-secondary btn-sm copy-button" title="Copy account handle" i18n-title | ||
32 | > | ||
33 | <span class="glyphicon glyphicon-duplicate"></span> | ||
34 | </button> | ||
35 | </div> | ||
36 | |||
37 | <div class="actor-counters"> | ||
38 | <span i18n>{naiveAggregatedSubscribers(), plural, =1 {1 subscriber} other {{{ naiveAggregatedSubscribers() }} subscribers}}</span> | ||
39 | |||
40 | <span class="videos-count" *ngIf="accountVideosCount !== undefined" i18n> | ||
41 | {accountVideosCount, plural, =1 {1 videos} other {{{ accountVideosCount }} videos}} | ||
42 | </span> | ||
43 | </div> | ||
17 | </div> | 44 | </div> |
18 | <span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span> | ||
19 | <span *ngIf="account.mutedByUser" class="badge badge-danger" i18n>Muted</span> | ||
20 | <span *ngIf="account.mutedServerByUser" class="badge badge-danger" i18n>Instance muted</span> | ||
21 | <span *ngIf="account.mutedByInstance" class="badge badge-danger" i18n>Muted by your instance</span> | ||
22 | <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span> | ||
23 | |||
24 | <my-user-moderation-dropdown | ||
25 | [prependActions]="prependModerationActions" | ||
26 | buttonSize="small" [account]="account" [user]="accountUser" placement="bottom-left auto" | ||
27 | (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()" | ||
28 | ></my-user-moderation-dropdown> | ||
29 | </div> | ||
30 | <div class="actor-followers" [title]="accountFollowerTitle"> | ||
31 | {{ subscribersDisplayFor(naiveAggregatedSubscribers) }} | ||
32 | </div> | 45 | </div> |
33 | </div> | 46 | </div> |
47 | </div> | ||
34 | 48 | ||
35 | <div class="right-buttons"> | 49 | <div class="description" [ngClass]="{ expanded: accountDescriptionExpanded }"> |
36 | <a *ngIf="isAccountManageable && !isInSmallView" routerLink="/my-account" class="btn btn-outline-tertiary mr-2" i18n>Manage account</a> | 50 | <div class="description-html" [innerHTML]="accountDescriptionHTML"></div> |
37 | <my-subscribe-button *ngIf="videoChannels" [account]="account" [videoChannels]="videoChannels"></my-subscribe-button> | 51 | |
38 | </div> | 52 | <div class="created-at" i18n>Account created on {{ account.createdAt | date }}</div> |
39 | </div> | 53 | </div> |
40 | 54 | ||
41 | <div class="links w-100"> | 55 | <div *ngIf="!accountDescriptionExpanded" class="show-more" role="button" |
42 | <ng-template #linkTemplate let-item="item"> | 56 | (click)="accountDescriptionExpanded = !accountDescriptionExpanded" |
43 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> | 57 | title="Show the complete description" i18n-title i18n |
44 | </ng-template> | 58 | > |
59 | Show more... | ||
60 | </div> | ||
45 | 61 | ||
46 | <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow> | 62 | <div class="buttons"> |
63 | <a *ngIf="isManageable() && !isInSmallView()" routerLink="/my-account" class="peertube-button-link orange-button" i18n> | ||
64 | Manage account | ||
65 | </a> | ||
47 | 66 | ||
48 | <simple-search-input (searchChanged)="searchChanged($event)" name="search-videos" i18n-placeholder placeholder="Search videos"></simple-search-input> | 67 | <my-subscribe-button *ngIf="videoChannels" [account]="account" [videoChannels]="videoChannels"></my-subscribe-button> |
49 | </div> | 68 | </div> |
50 | </div> | 69 | </div> |
51 | 70 | ||
52 | <div class="margin-content"> | 71 | <div class="links"> |
53 | <router-outlet (activate)="onOutletLoaded($event)"></router-outlet> | 72 | <ng-template #linkTemplate let-item="item"> |
73 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> | ||
74 | </ng-template> | ||
75 | |||
76 | <list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></list-overflow> | ||
77 | |||
78 | <simple-search-input | ||
79 | [alwaysShow]="!isInSmallView()" (searchChanged)="searchChanged($event)" | ||
80 | (inputDisplayChanged)="onSearchInputDisplayChanged($event)" name="search-videos" | ||
81 | i18n-iconTitle icon-title="Search account videos" | ||
82 | i18n-placeholder placeholder="Search account videos" | ||
83 | ></simple-search-input> | ||
54 | </div> | 84 | </div> |
85 | |||
86 | <router-outlet (activate)="onOutletLoaded($event)"></router-outlet> | ||
55 | </div> | 87 | </div> |
56 | 88 | ||
57 | <ng-container *ngIf="prependModerationActions"> | 89 | <ng-container *ngIf="prependModerationActions"> |
diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss index 40c6b6493..c1cf53f3a 100644 --- a/client/src/app/+accounts/accounts.component.scss +++ b/client/src/app/+accounts/accounts.component.scss | |||
@@ -1,49 +1,26 @@ | |||
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'; |
9 | 3 | @import '_actor'; | |
10 | .sub-menu { | 4 | @import '_miniature'; |
11 | @include sub-menu-with-actor; | 5 | |
12 | 6 | .root { | |
13 | .actor { | 7 | --myGlobalPadding: 60px; |
14 | width: 100%; | 8 | --myImgMargin: 30px; |
15 | } | 9 | --myFontSize: 16px; |
10 | --myGreyFontSize: 16px; | ||
16 | } | 11 | } |
17 | 12 | ||
18 | .margin-content { | 13 | .section-label { |
19 | // margin-content is required, but child views have their own margins | 14 | @include section-label-responsive; |
20 | // that match views outside the scope of accounts, so we only align | ||
21 | // them with the margins of .sub-menu when required. | ||
22 | margin: 0; | ||
23 | } | 15 | } |
24 | 16 | ||
25 | .right-buttons { | 17 | .links { |
26 | display: flex; | 18 | @include fluid-videos-miniature-layout; |
27 | height: max-content; | ||
28 | margin-left: auto; | ||
29 | margin-top: 10px; | ||
30 | |||
31 | @include media-breakpoint-down(lg) { | ||
32 | flex-flow: column-reverse; | ||
33 | 19 | ||
34 | a { | 20 | display: flex; |
35 | margin-top: 0.25rem; | 21 | justify-content: space-between; |
36 | margin-right: 0 !important; | 22 | align-items: center; |
37 | } | 23 | max-width: 800px; |
38 | } | ||
39 | |||
40 | a { | ||
41 | @include peertube-button-outline; | ||
42 | } | ||
43 | |||
44 | my-subscribe-button { | ||
45 | min-height: 30px; | ||
46 | } | ||
47 | } | 24 | } |
48 | 25 | ||
49 | my-user-moderation-dropdown, | 26 | my-user-moderation-dropdown, |
@@ -60,39 +37,98 @@ my-user-moderation-dropdown, | |||
60 | 37 | ||
61 | .copy-button { | 38 | .copy-button { |
62 | border: none; | 39 | border: none; |
63 | padding: 5px; | 40 | } |
64 | margin-top: -2px; | 41 | |
42 | .account-info { | ||
43 | display: grid; | ||
44 | grid-template-columns: 1fr min-content; | ||
45 | grid-template-rows: auto auto; | ||
46 | |||
47 | background-color: pvar(--submenuColor); | ||
48 | margin-bottom: 45px; | ||
49 | padding: var(--myGlobalPadding) var(--myGlobalPadding) 0 var(--myGlobalPadding); | ||
50 | font-size: var(--myFontSize); | ||
51 | } | ||
52 | |||
53 | .account-avatar-row { | ||
54 | @include avatar-row-responsive(var(--myImgMargin), var(--myGreyFontSize)); | ||
55 | } | ||
56 | |||
57 | .description { | ||
58 | grid-column: 1 / 3; | ||
59 | } | ||
60 | |||
61 | .created-at { | ||
62 | margin-top: 15px; | ||
63 | color: pvar(--greyForegroundColor); | ||
64 | padding-bottom: 60px; | ||
65 | } | ||
66 | |||
67 | .show-more { | ||
68 | @include show-more-description; | ||
69 | |||
70 | display: none; | ||
71 | text-align: center; | ||
72 | } | ||
73 | |||
74 | .buttons { | ||
75 | grid-column: 2; | ||
76 | grid-row: 1; | ||
77 | |||
78 | display: flex; | ||
79 | flex-wrap: wrap; | ||
80 | justify-content: flex-end; | ||
81 | align-content: flex-start; | ||
82 | |||
83 | > *:not(:last-child) { | ||
84 | margin-bottom: 15px; | ||
85 | } | ||
86 | } | ||
87 | |||
88 | @media screen and (max-width: $small-view) { | ||
89 | .root { | ||
90 | --myGlobalPadding: 45px; | ||
91 | --myChannelImgMargin: 15px; | ||
92 | } | ||
93 | |||
94 | .account-info { | ||
95 | display: block; | ||
96 | padding-bottom: 60px; | ||
97 | } | ||
98 | |||
99 | .description:not(.expanded) { | ||
100 | max-height: 70px; | ||
101 | |||
102 | @include fade-text(30px, pvar(--submenuColor)); | ||
103 | } | ||
104 | |||
105 | .show-more { | ||
106 | display: block; | ||
107 | } | ||
108 | |||
109 | .buttons { | ||
110 | justify-content: center; | ||
111 | } | ||
65 | } | 112 | } |
66 | 113 | ||
67 | @media screen and (max-width: $mobile-view) { | 114 | @media screen and (max-width: $mobile-view) { |
68 | .sub-menu { | 115 | .root { |
69 | .actor { | 116 | --myGlobalPadding: 15px; |
70 | flex-direction: column; | 117 | --myFontSize: 14px; |
71 | align-items: center; | 118 | --myGreyFontSize: 13px; |
72 | 119 | } | |
73 | img, | 120 | |
74 | .actor-info .actor-names .actor-display-name { | 121 | .account-info { |
75 | margin-right: 0; | 122 | display: block; |
76 | } | 123 | padding-bottom: 30px; |
77 | 124 | } | |
78 | .actor-info { | 125 | |
79 | .actor-names { | 126 | .links { |
80 | flex-direction: column; | 127 | margin: auto !important; |
81 | align-items: center; | 128 | width: min-content; |
82 | } | 129 | } |
83 | 130 | ||
84 | my-user-moderation-dropdown { | 131 | .show-more { |
85 | margin-left: 0; | 132 | margin-bottom: 30px; |
86 | } | ||
87 | |||
88 | .actor-followers { | ||
89 | text-align: center; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | .right-buttons { | ||
94 | margin-left: 0; | ||
95 | } | ||
96 | } | ||
97 | } | 133 | } |
98 | } | 134 | } |
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index e6a5a5d5e..a00063129 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts | |||
@@ -2,11 +2,19 @@ import { Subscription } from 'rxjs' | |||
2 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' | 2 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' |
3 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute } from '@angular/router' | 4 | import { ActivatedRoute } from '@angular/router' |
5 | import { AuthService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core' | 5 | import { AuthService, MarkdownService, Notifier, RedirectService, RestExtractor, ScreenService, UserService } from '@app/core' |
6 | import { Account, AccountService, DropdownAction, ListOverflowItem, VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 6 | import { |
7 | Account, | ||
8 | AccountService, | ||
9 | DropdownAction, | ||
10 | ListOverflowItem, | ||
11 | VideoChannel, | ||
12 | VideoChannelService, | ||
13 | VideoService | ||
14 | } from '@app/shared/shared-main' | ||
7 | import { AccountReportComponent } from '@app/shared/shared-moderation' | 15 | import { AccountReportComponent } from '@app/shared/shared-moderation' |
8 | import { User, UserRight } from '@shared/models' | ||
9 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | 16 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
17 | import { User, UserRight } from '@shared/models' | ||
10 | import { AccountSearchComponent } from './account-search/account-search.component' | 18 | import { AccountSearchComponent } from './account-search/account-search.component' |
11 | 19 | ||
12 | @Component({ | 20 | @Component({ |
@@ -15,16 +23,23 @@ import { AccountSearchComponent } from './account-search/account-search.componen | |||
15 | }) | 23 | }) |
16 | export class AccountsComponent implements OnInit, OnDestroy { | 24 | export class AccountsComponent implements OnInit, OnDestroy { |
17 | @ViewChild('accountReportModal') accountReportModal: AccountReportComponent | 25 | @ViewChild('accountReportModal') accountReportModal: AccountReportComponent |
26 | |||
18 | accountSearch: AccountSearchComponent | 27 | accountSearch: AccountSearchComponent |
19 | 28 | ||
20 | account: Account | 29 | account: Account |
21 | accountUser: User | 30 | accountUser: User |
31 | |||
22 | videoChannels: VideoChannel[] = [] | 32 | videoChannels: VideoChannel[] = [] |
33 | |||
23 | links: ListOverflowItem[] = [] | 34 | links: ListOverflowItem[] = [] |
35 | hideMenu = false | ||
24 | 36 | ||
25 | isAccountManageable = false | ||
26 | accountFollowerTitle = '' | 37 | accountFollowerTitle = '' |
27 | 38 | ||
39 | accountVideosCount: number | ||
40 | accountDescriptionHTML = '' | ||
41 | accountDescriptionExpanded = false | ||
42 | |||
28 | prependModerationActions: DropdownAction<any>[] | 43 | prependModerationActions: DropdownAction<any>[] |
29 | 44 | ||
30 | private routeSub: Subscription | 45 | private routeSub: Subscription |
@@ -38,6 +53,8 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
38 | private restExtractor: RestExtractor, | 53 | private restExtractor: RestExtractor, |
39 | private redirectService: RedirectService, | 54 | private redirectService: RedirectService, |
40 | private authService: AuthService, | 55 | private authService: AuthService, |
56 | private videoService: VideoService, | ||
57 | private markdown: MarkdownService, | ||
41 | private screenService: ScreenService | 58 | private screenService: ScreenService |
42 | ) { | 59 | ) { |
43 | } | 60 | } |
@@ -63,8 +80,7 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
63 | 80 | ||
64 | this.links = [ | 81 | this.links = [ |
65 | { label: $localize`VIDEO CHANNELS`, routerLink: 'video-channels' }, | 82 | { label: $localize`VIDEO CHANNELS`, routerLink: 'video-channels' }, |
66 | { label: $localize`VIDEOS`, routerLink: 'videos' }, | 83 | { label: $localize`VIDEOS`, routerLink: 'videos' } |
67 | { label: $localize`ABOUT`, routerLink: 'about' } | ||
68 | ] | 84 | ] |
69 | } | 85 | } |
70 | 86 | ||
@@ -72,19 +88,29 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
72 | if (this.routeSub) this.routeSub.unsubscribe() | 88 | if (this.routeSub) this.routeSub.unsubscribe() |
73 | } | 89 | } |
74 | 90 | ||
75 | get naiveAggregatedSubscribers () { | 91 | naiveAggregatedSubscribers () { |
76 | return this.videoChannels.reduce( | 92 | return this.videoChannels.reduce( |
77 | (acc, val) => acc + val.followersCount, | 93 | (acc, val) => acc + val.followersCount, |
78 | this.account.followersCount // accumulator starts with the base number of subscribers the account has | 94 | this.account.followersCount // accumulator starts with the base number of subscribers the account has |
79 | ) | 95 | ) |
80 | } | 96 | } |
81 | 97 | ||
82 | get isInSmallView () { | 98 | isUserLoggedIn () { |
99 | return this.authService.isLoggedIn() | ||
100 | } | ||
101 | |||
102 | isInSmallView () { | ||
83 | return this.screenService.isInSmallView() | 103 | return this.screenService.isInSmallView() |
84 | } | 104 | } |
85 | 105 | ||
106 | isManageable () { | ||
107 | if (!this.isUserLoggedIn()) return false | ||
108 | |||
109 | return this.account?.userId === this.authService.getUser().id | ||
110 | } | ||
111 | |||
86 | onUserChanged () { | 112 | onUserChanged () { |
87 | this.getUserIfNeeded(this.account) | 113 | this.loadUserIfNeeded(this.account) |
88 | } | 114 | } |
89 | 115 | ||
90 | onUserDeleted () { | 116 | onUserDeleted () { |
@@ -113,40 +139,30 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
113 | if (this.accountSearch) this.accountSearch.updateSearch(search) | 139 | if (this.accountSearch) this.accountSearch.updateSearch(search) |
114 | } | 140 | } |
115 | 141 | ||
116 | private onAccount (account: Account) { | 142 | onSearchInputDisplayChanged (displayed: boolean) { |
143 | this.hideMenu = this.isInSmallView() && displayed | ||
144 | } | ||
145 | |||
146 | private async onAccount (account: Account) { | ||
147 | this.accountFollowerTitle = $localize`${account.followersCount} direct account followers` | ||
148 | |||
117 | this.prependModerationActions = undefined | 149 | this.prependModerationActions = undefined |
118 | 150 | ||
119 | this.account = account | 151 | this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description) |
120 | 152 | ||
121 | if (this.authService.isLoggedIn()) { | 153 | // After the markdown renderer to avoid layout changes |
122 | this.authService.userInformationLoaded.subscribe( | 154 | this.account = account |
123 | () => { | ||
124 | this.isAccountManageable = this.account.userId && this.account.userId === this.authService.getUser().id | ||
125 | |||
126 | const followers = this.subscribersDisplayFor(account.followersCount) | ||
127 | this.accountFollowerTitle = $localize`${followers} direct account followers` | ||
128 | |||
129 | // It's not our account, we can report it | ||
130 | if (!this.isAccountManageable) { | ||
131 | this.prependModerationActions = [ | ||
132 | { | ||
133 | label: $localize`Report this account`, | ||
134 | handler: () => this.showReportModal() | ||
135 | } | ||
136 | ] | ||
137 | } | ||
138 | } | ||
139 | ) | ||
140 | } | ||
141 | 155 | ||
142 | this.getUserIfNeeded(account) | 156 | this.updateModerationActions() |
157 | this.loadUserIfNeeded(account) | ||
158 | this.loadAccountVideosCount() | ||
143 | } | 159 | } |
144 | 160 | ||
145 | private showReportModal () { | 161 | private showReportModal () { |
146 | this.accountReportModal.show() | 162 | this.accountReportModal.show() |
147 | } | 163 | } |
148 | 164 | ||
149 | private getUserIfNeeded (account: Account) { | 165 | private loadUserIfNeeded (account: Account) { |
150 | if (!account.userId || !this.authService.isLoggedIn()) return | 166 | if (!account.userId || !this.authService.isLoggedIn()) return |
151 | 167 | ||
152 | const user = this.authService.getUser() | 168 | const user = this.authService.getUser() |
@@ -158,4 +174,33 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
158 | ) | 174 | ) |
159 | } | 175 | } |
160 | } | 176 | } |
177 | |||
178 | private updateModerationActions () { | ||
179 | if (!this.authService.isLoggedIn()) return | ||
180 | |||
181 | this.authService.userInformationLoaded.subscribe( | ||
182 | () => { | ||
183 | if (this.isManageable()) return | ||
184 | |||
185 | // It's not our account, we can report it | ||
186 | this.prependModerationActions = [ | ||
187 | { | ||
188 | label: $localize`Report this account`, | ||
189 | handler: () => this.showReportModal() | ||
190 | } | ||
191 | ] | ||
192 | } | ||
193 | ) | ||
194 | } | ||
195 | |||
196 | private loadAccountVideosCount () { | ||
197 | this.videoService.getAccountVideos({ | ||
198 | account: this.account, | ||
199 | videoPagination: { | ||
200 | currentPage: 1, | ||
201 | itemsPerPage: 0 | ||
202 | }, | ||
203 | sort: '-publishedAt' | ||
204 | }).subscribe(res => this.accountVideosCount = res.total) | ||
205 | } | ||
161 | } | 206 | } |
diff --git a/client/src/app/+accounts/accounts.module.ts b/client/src/app/+accounts/accounts.module.ts index 6da65cbc1..3354b4189 100644 --- a/client/src/app/+accounts/accounts.module.ts +++ b/client/src/app/+accounts/accounts.module.ts | |||
@@ -5,10 +5,9 @@ import { SharedMainModule } from '@app/shared/shared-main' | |||
5 | import { SharedModerationModule } from '@app/shared/shared-moderation' | 5 | import { SharedModerationModule } from '@app/shared/shared-moderation' |
6 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' | 6 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' |
7 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' | 7 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' |
8 | import { AccountAboutComponent } from './account-about/account-about.component' | 8 | import { AccountSearchComponent } from './account-search/account-search.component' |
9 | import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component' | 9 | import { AccountVideoChannelsComponent } from './account-video-channels/account-video-channels.component' |
10 | import { AccountVideosComponent } from './account-videos/account-videos.component' | 10 | import { AccountVideosComponent } from './account-videos/account-videos.component' |
11 | import { AccountSearchComponent } from './account-search/account-search.component' | ||
12 | import { AccountsRoutingModule } from './accounts-routing.module' | 11 | import { AccountsRoutingModule } from './accounts-routing.module' |
13 | import { AccountsComponent } from './accounts.component' | 12 | import { AccountsComponent } from './accounts.component' |
14 | 13 | ||
@@ -28,7 +27,6 @@ import { AccountsComponent } from './accounts.component' | |||
28 | AccountsComponent, | 27 | AccountsComponent, |
29 | AccountVideosComponent, | 28 | AccountVideosComponent, |
30 | AccountVideoChannelsComponent, | 29 | AccountVideoChannelsComponent, |
31 | AccountAboutComponent, | ||
32 | AccountSearchComponent | 30 | AccountSearchComponent |
33 | ], | 31 | ], |
34 | 32 | ||