diff options
Diffstat (limited to 'client/src/app/+accounts')
13 files changed, 510 insertions, 274 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/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html index 5dbb341d2..19a4b3c9c 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html | |||
@@ -1,33 +1,50 @@ | |||
1 | <h1 class="sr-only" i18n>Video channels</h1> | 1 | <h1 class="sr-only" i18n>Video channels</h1> |
2 | |||
2 | <div class="margin-content"> | 3 | <div class="margin-content"> |
3 | 4 | ||
4 | <div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div> | 5 | <div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div> |
5 | 6 | ||
6 | <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onChannelDataSubject.asObservable()"> | 7 | <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onChannelDataSubject.asObservable()"> |
7 | <div class="section channel" *ngFor="let videoChannel of videoChannels"> | 8 | <div class="channel" *ngFor="let videoChannel of videoChannels"> |
8 | <div class="section-title"> | 9 | |
9 | <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> | 10 | <div class="channel-avatar-row"> |
11 | <a class="avatar-link" [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> | ||
10 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 12 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
13 | </a> | ||
11 | 14 | ||
12 | <h2 class="section-title">{{ videoChannel.displayName }}</h2> | 15 | <h2> |
16 | <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> | ||
17 | {{ videoChannel.displayName }} | ||
18 | </a> | ||
19 | </h2> | ||
20 | |||
21 | <div class="actor-counters"> | ||
13 | <div class="followers" i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> | 22 | <div class="followers" i18n>{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> |
14 | </a> | ||
15 | 23 | ||
16 | <my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button> | 24 | <span class="videos-count" *ngIf="getTotalVideosOf(videoChannel) !== undefined" i18n> |
25 | {getTotalVideosOf(videoChannel), plural, =1 {1 videos} other {{{ getTotalVideosOf(videoChannel) }} videos}} | ||
26 | </span> | ||
27 | </div> | ||
28 | |||
29 | <div class="description-html" [innerHTML]="getChannelDescription(videoChannel)"></div> | ||
17 | </div> | 30 | </div> |
18 | 31 | ||
19 | <div *ngIf="getVideosOf(videoChannel)" class="videos"> | 32 | <my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button> |
20 | <div class="no-results my-5" i18n *ngIf="getVideosOf(videoChannel).length === 0">This channel doesn't have any videos.</div> | 33 | |
34 | <a i18n class="button-show-channel peertube-button-link orange-button-inverted" [routerLink]="getVideoChannelLink(videoChannel)">Show this channel</a> | ||
35 | |||
36 | <div class="videos"> | ||
37 | <div class="no-results" i18n *ngIf="getTotalVideosOf(videoChannel) === 0">This channel doesn't have any videos.</div> | ||
21 | 38 | ||
22 | <my-video-miniature | 39 | <my-video-miniature |
23 | *ngFor="let video of getVideosOf(videoChannel)" | 40 | *ngFor="let video of getVideosOf(videoChannel)" |
24 | [video]="video" [user]="userMiniature" [displayVideoActions]="true" | 41 | [video]="video" [user]="userMiniature" [displayVideoActions]="true" [displayOptions]="miniatureDisplayOptions" |
25 | ></my-video-miniature> | 42 | ></my-video-miniature> |
26 | </div> | ||
27 | 43 | ||
28 | <a *ngIf="getVideosOf(videoChannel).length !== 0" class="show-more" i18n [routerLink]="getVideoChannelLink(videoChannel)"> | 44 | <div *ngIf="getTotalVideosOf(videoChannel)" class="miniature-show-channel"> |
29 | SHOW THIS CHANNEL | 45 | <a i18n [routerLink]="getVideoChannelLink(videoChannel)">SHOW THIS CHANNEL ></a> |
30 | </a> | 46 | </div> |
47 | </div> | ||
31 | </div> | 48 | </div> |
32 | </div> | 49 | </div> |
33 | </div> | 50 | </div> |
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss index 4957e91d7..7e88802f3 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss | |||
@@ -3,37 +3,175 @@ | |||
3 | @import '_miniature'; | 3 | @import '_miniature'; |
4 | 4 | ||
5 | .margin-content { | 5 | .margin-content { |
6 | @include fluid-videos-miniature-layout; | 6 | @include grid-videos-miniature-margins; |
7 | } | 7 | } |
8 | 8 | ||
9 | .section { | 9 | .channel { |
10 | @include miniature-rows; | 10 | max-width: $max-channels-width; |
11 | background-color: pvar(--channelBackgroundColor); | ||
12 | padding: 30px; | ||
11 | 13 | ||
12 | padding-top: 0 !important; | 14 | margin: 30px 0; |
13 | 15 | ||
14 | .section-title { | 16 | display: grid; |
17 | grid-template-columns: 1fr auto; | ||
18 | grid-template-rows: auto auto; | ||
19 | column-gap: 15px; | ||
20 | } | ||
21 | |||
22 | .channel-avatar-row { | ||
23 | grid-column: 1; | ||
24 | grid-row: 1; | ||
25 | |||
26 | display: grid; | ||
27 | grid-template-columns: auto auto 1fr; | ||
28 | grid-template-rows: auto 1fr; | ||
29 | |||
30 | .avatar-link { | ||
31 | grid-column: 1; | ||
32 | grid-row: 1 / 3; | ||
33 | margin-right: 30px; | ||
34 | } | ||
35 | |||
36 | img { | ||
37 | @include channel-avatar(75px); | ||
38 | } | ||
39 | |||
40 | a { | ||
41 | color: pvar(--mainForegroundColor); | ||
42 | } | ||
43 | |||
44 | h2 { | ||
45 | grid-row: 1; | ||
46 | grid-column: 2; | ||
47 | font-size: 20px; | ||
48 | line-height: 1; | ||
49 | font-weight: $font-bold; | ||
50 | margin: 0; | ||
51 | } | ||
52 | |||
53 | .actor-counters { | ||
54 | grid-row: 1; | ||
55 | grid-column: 3; | ||
56 | color: pvar(--greyForegroundColor); | ||
57 | font-size: 16px; | ||
58 | display: flex; | ||
15 | align-items: center; | 59 | align-items: center; |
60 | margin-left: 15px; | ||
16 | } | 61 | } |
17 | 62 | ||
18 | .videos { | 63 | .actor-counters > *:not(:last-child)::after { |
19 | overflow: hidden; | 64 | content: '•'; |
65 | margin: 0 10px; | ||
66 | color: pvar(--mainColor); | ||
67 | } | ||
20 | 68 | ||
21 | .no-results { | 69 | .description-html { |
22 | height: 50px; | 70 | grid-column: 2 / 4; |
23 | } | 71 | grid-row: 2; |
72 | |||
73 | max-height: 80px; | ||
74 | font-size: 16px; | ||
75 | |||
76 | @include fade-text(30px, pvar(--channelBackgroundColor)); | ||
77 | } | ||
78 | } | ||
79 | |||
80 | my-subscribe-button { | ||
81 | grid-row: 1; | ||
82 | grid-column: 2; | ||
83 | } | ||
84 | |||
85 | .videos { | ||
86 | display: flex; | ||
87 | grid-column: 1 / 3; | ||
88 | grid-row: 2; | ||
89 | margin-top: 30px; | ||
90 | |||
91 | position: relative; | ||
92 | overflow: hidden; | ||
93 | |||
94 | my-video-miniature { | ||
95 | margin-right: 15px; | ||
96 | min-width: $video-thumbnail-medium-width; | ||
97 | max-width: $video-thumbnail-medium-width; | ||
98 | } | ||
99 | |||
100 | .no-results { | ||
101 | height: auto; | ||
24 | } | 102 | } |
103 | } | ||
25 | 104 | ||
26 | my-video-miniature ::ng-deep my-video-actions-dropdown > my-action-dropdown { | 105 | .miniature-show-channel { |
27 | // Fix our overflow | 106 | height: 100%; |
28 | position: absolute; | 107 | position: absolute; |
108 | right: 0; | ||
109 | background: linear-gradient(90deg, transparent 0, pvar(--channelBackgroundColor) 45px); | ||
110 | padding: ($video-thumbnail-medium-height / 2 - 10px) 15px 0 60px; | ||
111 | z-index: z(miniature) + 1; | ||
112 | |||
113 | a { | ||
114 | color: pvar(--mainColor); | ||
115 | font-size: 16px; | ||
116 | font-weight: $font-semibold; | ||
29 | } | 117 | } |
30 | } | 118 | } |
31 | 119 | ||
120 | .button-show-channel { | ||
121 | display: none; | ||
122 | } | ||
123 | |||
32 | @media screen and (max-width: $mobile-view) { | 124 | @media screen and (max-width: $mobile-view) { |
33 | .section { | 125 | .channel { |
34 | .section-title { | 126 | padding: 15px; |
35 | flex-direction: column; | 127 | } |
36 | align-items: normal; | 128 | |
129 | .channel-avatar-row { | ||
130 | grid-template-columns: auto auto auto 1fr; | ||
131 | |||
132 | .avatar-link { | ||
133 | grid-row: 1 / 4; | ||
134 | } | ||
135 | |||
136 | h2 { | ||
137 | font-size: 16px; | ||
37 | } | 138 | } |
139 | |||
140 | .actor-counters { | ||
141 | margin: 0; | ||
142 | font-size: 13px; | ||
143 | grid-row: 2; | ||
144 | grid-column: 2 / 4; | ||
145 | } | ||
146 | |||
147 | .description-html { | ||
148 | grid-row: 3; | ||
149 | font-size: 14px; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | .show-channel a { | ||
154 | @include peertube-button-link; | ||
155 | @include orange-button-inverted; | ||
156 | } | ||
157 | |||
158 | .videos { | ||
159 | display: none; | ||
160 | } | ||
161 | |||
162 | my-subscribe-button, | ||
163 | .button-show-channel { | ||
164 | grid-column: 1 / 4; | ||
165 | grid-row: 3; | ||
166 | margin-top: 15px; | ||
167 | } | ||
168 | |||
169 | my-subscribe-button { | ||
170 | justify-self: start; | ||
171 | } | ||
172 | |||
173 | .button-show-channel { | ||
174 | display: block; | ||
175 | justify-self: end; | ||
38 | } | 176 | } |
39 | } | 177 | } |
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts index f2beb6689..0628c7a96 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { from, Subject, Subscription } from 'rxjs' | 1 | import { from, Subject, Subscription } from 'rxjs' |
2 | import { concatMap, map, switchMap, tap } from 'rxjs/operators' | 2 | import { concatMap, map, switchMap, tap } from 'rxjs/operators' |
3 | import { Component, OnDestroy, OnInit } from '@angular/core' | 3 | import { Component, OnDestroy, OnInit } from '@angular/core' |
4 | import { ComponentPagination, hasMoreItems, ScreenService, User, UserService } from '@app/core' | 4 | import { ComponentPagination, hasMoreItems, MarkdownService, ScreenService, User, UserService } from '@app/core' |
5 | import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | 5 | import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' |
6 | import { NSFWPolicyType, VideoSortField } from '@shared/models' | 6 | import { NSFWPolicyType, VideoSortField } from '@shared/models' |
7 | import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' | ||
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-account-video-channels', | 10 | selector: 'my-account-video-channels', |
@@ -13,7 +14,10 @@ import { NSFWPolicyType, VideoSortField } from '@shared/models' | |||
13 | export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | 14 | export class AccountVideoChannelsComponent implements OnInit, OnDestroy { |
14 | account: Account | 15 | account: Account |
15 | videoChannels: VideoChannel[] = [] | 16 | videoChannels: VideoChannel[] = [] |
16 | videos: { [id: number]: Video[] } = {} | 17 | |
18 | videos: { [id: number]: { total: number, videos: Video[] } } = {} | ||
19 | |||
20 | channelsDescriptionHTML: { [ id: number ]: string } = {} | ||
17 | 21 | ||
18 | channelPagination: ComponentPagination = { | 22 | channelPagination: ComponentPagination = { |
19 | currentPage: 1, | 23 | currentPage: 1, |
@@ -23,7 +27,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
23 | 27 | ||
24 | videosPagination: ComponentPagination = { | 28 | videosPagination: ComponentPagination = { |
25 | currentPage: 1, | 29 | currentPage: 1, |
26 | itemsPerPage: 12, | 30 | itemsPerPage: 5, |
27 | totalItems: null | 31 | totalItems: null |
28 | } | 32 | } |
29 | videosSort: VideoSortField = '-publishedAt' | 33 | videosSort: VideoSortField = '-publishedAt' |
@@ -32,6 +36,16 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
32 | 36 | ||
33 | userMiniature: User | 37 | userMiniature: User |
34 | nsfwPolicy: NSFWPolicyType | 38 | nsfwPolicy: NSFWPolicyType |
39 | miniatureDisplayOptions: MiniatureDisplayOptions = { | ||
40 | date: true, | ||
41 | views: true, | ||
42 | by: false, | ||
43 | avatar: false, | ||
44 | privacyLabel: false, | ||
45 | privacyText: false, | ||
46 | state: false, | ||
47 | blacklistInfo: false | ||
48 | } | ||
35 | 49 | ||
36 | private accountSub: Subscription | 50 | private accountSub: Subscription |
37 | 51 | ||
@@ -39,7 +53,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
39 | private accountService: AccountService, | 53 | private accountService: AccountService, |
40 | private videoChannelService: VideoChannelService, | 54 | private videoChannelService: VideoChannelService, |
41 | private videoService: VideoService, | 55 | private videoService: VideoService, |
42 | private screenService: ScreenService, | 56 | private markdown: MarkdownService, |
43 | private userService: UserService | 57 | private userService: UserService |
44 | ) { } | 58 | ) { } |
45 | 59 | ||
@@ -78,23 +92,36 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy { | |||
78 | } | 92 | } |
79 | 93 | ||
80 | return this.videoService.getVideoChannelVideos(options) | 94 | return this.videoService.getVideoChannelVideos(options) |
81 | .pipe(map(data => ({ videoChannel, videos: data.data }))) | 95 | .pipe(map(data => ({ videoChannel, videos: data.data, total: data.total }))) |
82 | }) | 96 | }) |
83 | ) | 97 | ) |
84 | .subscribe(({ videoChannel, videos }) => { | 98 | .subscribe(async ({ videoChannel, videos, total }) => { |
99 | this.channelsDescriptionHTML[videoChannel.id] = await this.markdown.textMarkdownToHTML(videoChannel.description) | ||
100 | |||
85 | this.videoChannels.push(videoChannel) | 101 | this.videoChannels.push(videoChannel) |
86 | 102 | ||
87 | this.videos[videoChannel.id] = videos | 103 | this.videos[videoChannel.id] = { videos, total } |
88 | 104 | ||
89 | this.onChannelDataSubject.next([ videoChannel ]) | 105 | this.onChannelDataSubject.next([ videoChannel ]) |
90 | }) | 106 | }) |
91 | } | 107 | } |
92 | 108 | ||
93 | getVideosOf (videoChannel: VideoChannel) { | 109 | getVideosOf (videoChannel: VideoChannel) { |
94 | const numberOfVideos = this.screenService.getNumberOfAvailableMiniatures() | 110 | const obj = this.videos[ videoChannel.id ] |
111 | if (!obj) return [] | ||
112 | |||
113 | return obj.videos | ||
114 | } | ||
115 | |||
116 | getTotalVideosOf (videoChannel: VideoChannel) { | ||
117 | const obj = this.videos[ videoChannel.id ] | ||
118 | if (!obj) return undefined | ||
119 | |||
120 | return obj.total | ||
121 | } | ||
95 | 122 | ||
96 | // 2 rows | 123 | getChannelDescription (videoChannel: VideoChannel) { |
97 | return this.videos[ videoChannel.id ].slice(0, numberOfVideos * 2) | 124 | return this.channelsDescriptionHTML[videoChannel.id] |
98 | } | 125 | } |
99 | 126 | ||
100 | onNearOfBottom () { | 127 | onNearOfBottom () { |
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index 484d60e25..75af45e90 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts | |||
@@ -16,6 +16,7 @@ import { VideoFilter } from '@shared/models' | |||
16 | ] | 16 | ] |
17 | }) | 17 | }) |
18 | export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { | 18 | export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { |
19 | // No value because we don't want a page title | ||
19 | titlePage: string | 20 | titlePage: string |
20 | loadOnInit = false | 21 | loadOnInit = false |
21 | loadUserVideoPreferences = true | 22 | loadUserVideoPreferences = true |
@@ -77,11 +78,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
77 | 78 | ||
78 | return this.videoService | 79 | return this.videoService |
79 | .getAccountVideos(options) | 80 | .getAccountVideos(options) |
80 | .pipe( | ||
81 | tap(({ total }) => { | ||
82 | this.titlePage = $localize`Published ${total} videos` | ||
83 | }) | ||
84 | ) | ||
85 | } | 81 | } |
86 | 82 | ||
87 | toggleModerationDisplay () { | 83 | toggleModerationDisplay () { |
@@ -93,4 +89,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
93 | generateSyndicationList () { | 89 | generateSyndicationList () { |
94 | this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) | 90 | this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) |
95 | } | 91 | } |
92 | |||
93 | displayAsRow () { | ||
94 | return this.screenService.isInMobileView() | ||
95 | } | ||
96 | } | 96 | } |
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 5bd7b0824..ea7a317eb 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -1,57 +1,87 @@ | |||
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 | <my-account-avatar [account]="account" size="120"></my-account-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 i18n-title [title]="'Created on ' + (account.createdAt | date)">{{ account.displayName }}</h1> |
14 | > | 14 | |
15 | <span class="glyphicon glyphicon-copy"></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> | ||
38 | </div> | ||
39 | </div> | 51 | </div> |
40 | 52 | ||
41 | <div class="links w-100"> | 53 | <div *ngIf="hasShowMoreDescription()" class="show-more" role="button" |
42 | <ng-template #linkTemplate let-item="item"> | 54 | (click)="accountDescriptionExpanded = !accountDescriptionExpanded" |
43 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> | 55 | title="Show the complete description" i18n-title i18n |
44 | </ng-template> | 56 | > |
57 | Show more... | ||
58 | </div> | ||
45 | 59 | ||
46 | <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow> | 60 | <div class="buttons"> |
61 | <a *ngIf="isManageable()" routerLink="/my-account" class="peertube-button-link orange-button" i18n> | ||
62 | Manage account | ||
63 | </a> | ||
47 | 64 | ||
48 | <simple-search-input (searchChanged)="searchChanged($event)" name="search-videos" i18n-placeholder placeholder="Search videos"></simple-search-input> | 65 | <my-subscribe-button *ngIf="hasVideoChannels() && !isManageable()" [account]="account" [videoChannels]="videoChannels"></my-subscribe-button> |
49 | </div> | 66 | </div> |
50 | </div> | 67 | </div> |
51 | 68 | ||
52 | <div class="margin-content"> | 69 | <div class="links"> |
53 | <router-outlet (activate)="onOutletLoaded($event)"></router-outlet> | 70 | <ng-template #linkTemplate let-item="item"> |
71 | <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a> | ||
72 | </ng-template> | ||
73 | |||
74 | <list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></list-overflow> | ||
75 | |||
76 | <simple-search-input | ||
77 | [alwaysShow]="!isInSmallView()" (searchChanged)="searchChanged($event)" | ||
78 | (inputDisplayChanged)="onSearchInputDisplayChanged($event)" name="search-videos" | ||
79 | i18n-iconTitle icon-title="Search account videos" | ||
80 | i18n-placeholder placeholder="Search account videos" | ||
81 | ></simple-search-input> | ||
54 | </div> | 82 | </div> |
83 | |||
84 | <router-outlet (activate)="onOutletLoaded($event)"></router-outlet> | ||
55 | </div> | 85 | </div> |
56 | 86 | ||
57 | <ng-container *ngIf="prependModerationActions"> | 87 | <ng-container *ngIf="prependModerationActions"> |
diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss index 40c6b6493..56927dea6 100644 --- a/client/src/app/+accounts/accounts.component.scss +++ b/client/src/app/+accounts/accounts.component.scss | |||
@@ -1,48 +1,29 @@ | |||
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 | --myGlobalTopPadding: 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 grid-videos-miniature-margins; |
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: $max-channels-width; |
38 | } | ||
39 | |||
40 | a { | ||
41 | @include peertube-button-outline; | ||
42 | } | ||
43 | 24 | ||
44 | my-subscribe-button { | 25 | simple-search-input { |
45 | min-height: 30px; | 26 | margin-left: auto; |
46 | } | 27 | } |
47 | } | 28 | } |
48 | 29 | ||
@@ -60,39 +41,101 @@ my-user-moderation-dropdown, | |||
60 | 41 | ||
61 | .copy-button { | 42 | .copy-button { |
62 | border: none; | 43 | border: none; |
63 | padding: 5px; | 44 | } |
64 | margin-top: -2px; | 45 | |
46 | .account-info { | ||
47 | @include grid-videos-miniature-margins(false, 15px); | ||
48 | |||
49 | display: grid; | ||
50 | grid-template-columns: 1fr min-content; | ||
51 | grid-template-rows: auto auto; | ||
52 | |||
53 | background-color: pvar(--submenuBackgroundColor); | ||
54 | margin-bottom: 45px; | ||
55 | padding-top: var(--myGlobalTopPadding); | ||
56 | padding-bottom: var(--myGlobalTopPadding); | ||
57 | font-size: var(--myFontSize); | ||
58 | } | ||
59 | |||
60 | .account-avatar-row { | ||
61 | @include avatar-row-responsive(var(--myImgMargin), var(--myGreyFontSize)); | ||
62 | } | ||
63 | |||
64 | .description { | ||
65 | grid-column: 1 / 3; | ||
66 | max-width: 1000px; | ||
67 | word-break: break-word; | ||
68 | } | ||
69 | |||
70 | .show-more { | ||
71 | @include show-more-description; | ||
72 | |||
73 | display: none; | ||
74 | text-align: center; | ||
75 | } | ||
76 | |||
77 | .buttons { | ||
78 | grid-column: 2; | ||
79 | grid-row: 1; | ||
80 | |||
81 | display: flex; | ||
82 | flex-wrap: wrap; | ||
83 | justify-content: flex-end; | ||
84 | align-content: flex-start; | ||
85 | |||
86 | > *:not(:last-child) { | ||
87 | margin-bottom: 15px; | ||
88 | } | ||
89 | |||
90 | > a { | ||
91 | white-space: nowrap; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | @media screen and (max-width: $small-view) { | ||
96 | .root { | ||
97 | --myGlobalTopPadding: 45px; | ||
98 | --myChannelImgMargin: 15px; | ||
99 | } | ||
100 | |||
101 | .account-info { | ||
102 | display: block; | ||
103 | padding-bottom: 60px; | ||
104 | } | ||
105 | |||
106 | .description:not(.expanded) { | ||
107 | max-height: 70px; | ||
108 | |||
109 | @include fade-text(30px, pvar(--submenuBackgroundColor)); | ||
110 | } | ||
111 | |||
112 | .show-more { | ||
113 | display: block; | ||
114 | } | ||
115 | |||
116 | .buttons { | ||
117 | justify-content: center; | ||
118 | } | ||
65 | } | 119 | } |
66 | 120 | ||
67 | @media screen and (max-width: $mobile-view) { | 121 | @media screen and (max-width: $mobile-view) { |
68 | .sub-menu { | 122 | .root { |
69 | .actor { | 123 | --myGlobalTopPadding: 15px; |
70 | flex-direction: column; | 124 | --myFontSize: 14px; |
71 | align-items: center; | 125 | --myGreyFontSize: 13px; |
72 | 126 | } | |
73 | img, | 127 | |
74 | .actor-info .actor-names .actor-display-name { | 128 | .account-info { |
75 | margin-right: 0; | 129 | display: block; |
76 | } | 130 | padding-bottom: 30px; |
77 | 131 | } | |
78 | .actor-info { | 132 | |
79 | .actor-names { | 133 | .links { |
80 | flex-direction: column; | 134 | margin: auto !important; |
81 | align-items: center; | 135 | width: min-content; |
82 | } | 136 | } |
83 | 137 | ||
84 | my-user-moderation-dropdown { | 138 | .show-more { |
85 | margin-left: 0; | 139 | 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 | } | 140 | } |
98 | } | 141 | } |
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index e6a5a5d5e..fbd7380a9 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 | } |
@@ -62,9 +79,8 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
62 | ) | 79 | ) |
63 | 80 | ||
64 | this.links = [ | 81 | this.links = [ |
65 | { label: $localize`VIDEO CHANNELS`, routerLink: 'video-channels' }, | 82 | { label: $localize`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,38 @@ 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 | hasVideoChannels () { | ||
147 | return this.videoChannels.length !== 0 | ||
148 | } | ||
149 | |||
150 | hasShowMoreDescription () { | ||
151 | return !this.accountDescriptionExpanded && this.accountDescriptionHTML.length > 100 | ||
152 | } | ||
153 | |||
154 | private async onAccount (account: Account) { | ||
155 | this.accountFollowerTitle = $localize`${account.followersCount} direct account followers` | ||
156 | |||
117 | this.prependModerationActions = undefined | 157 | this.prependModerationActions = undefined |
118 | 158 | ||
119 | this.account = account | 159 | this.accountDescriptionHTML = await this.markdown.textMarkdownToHTML(account.description) |
120 | 160 | ||
121 | if (this.authService.isLoggedIn()) { | 161 | // After the markdown renderer to avoid layout changes |
122 | this.authService.userInformationLoaded.subscribe( | 162 | 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 | 163 | ||
142 | this.getUserIfNeeded(account) | 164 | this.updateModerationActions() |
165 | this.loadUserIfNeeded(account) | ||
166 | this.loadAccountVideosCount() | ||
143 | } | 167 | } |
144 | 168 | ||
145 | private showReportModal () { | 169 | private showReportModal () { |
146 | this.accountReportModal.show() | 170 | this.accountReportModal.show() |
147 | } | 171 | } |
148 | 172 | ||
149 | private getUserIfNeeded (account: Account) { | 173 | private loadUserIfNeeded (account: Account) { |
150 | if (!account.userId || !this.authService.isLoggedIn()) return | 174 | if (!account.userId || !this.authService.isLoggedIn()) return |
151 | 175 | ||
152 | const user = this.authService.getUser() | 176 | const user = this.authService.getUser() |
@@ -158,4 +182,33 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
158 | ) | 182 | ) |
159 | } | 183 | } |
160 | } | 184 | } |
185 | |||
186 | private updateModerationActions () { | ||
187 | if (!this.authService.isLoggedIn()) return | ||
188 | |||
189 | this.authService.userInformationLoaded.subscribe( | ||
190 | () => { | ||
191 | if (this.isManageable()) return | ||
192 | |||
193 | // It's not our account, we can report it | ||
194 | this.prependModerationActions = [ | ||
195 | { | ||
196 | label: $localize`Report this account`, | ||
197 | handler: () => this.showReportModal() | ||
198 | } | ||
199 | ] | ||
200 | } | ||
201 | ) | ||
202 | } | ||
203 | |||
204 | private loadAccountVideosCount () { | ||
205 | this.videoService.getAccountVideos({ | ||
206 | account: this.account, | ||
207 | videoPagination: { | ||
208 | currentPage: 1, | ||
209 | itemsPerPage: 0 | ||
210 | }, | ||
211 | sort: '-publishedAt' | ||
212 | }).subscribe(res => this.accountVideosCount = res.total) | ||
213 | } | ||
161 | } | 214 | } |
diff --git a/client/src/app/+accounts/accounts.module.ts b/client/src/app/+accounts/accounts.module.ts index 6da65cbc1..22cdd0642 100644 --- a/client/src/app/+accounts/accounts.module.ts +++ b/client/src/app/+accounts/accounts.module.ts | |||
@@ -5,12 +5,12 @@ 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' |
13 | import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module' | ||
14 | 14 | ||
15 | @NgModule({ | 15 | @NgModule({ |
16 | imports: [ | 16 | imports: [ |
@@ -21,14 +21,14 @@ import { AccountsComponent } from './accounts.component' | |||
21 | SharedUserSubscriptionModule, | 21 | SharedUserSubscriptionModule, |
22 | SharedModerationModule, | 22 | SharedModerationModule, |
23 | SharedVideoMiniatureModule, | 23 | SharedVideoMiniatureModule, |
24 | SharedGlobalIconModule | 24 | SharedGlobalIconModule, |
25 | SharedAccountAvatarModule | ||
25 | ], | 26 | ], |
26 | 27 | ||
27 | declarations: [ | 28 | declarations: [ |
28 | AccountsComponent, | 29 | AccountsComponent, |
29 | AccountVideosComponent, | 30 | AccountVideosComponent, |
30 | AccountVideoChannelsComponent, | 31 | AccountVideoChannelsComponent, |
31 | AccountAboutComponent, | ||
32 | AccountSearchComponent | 32 | AccountSearchComponent |
33 | ], | 33 | ], |
34 | 34 | ||