From 4beda9e12adc7b1f3b178cecd6863ebf3cf431f1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 19 Oct 2021 09:44:43 +0200 Subject: Add ability to view my followers --- .../about-follows/about-follows.component.ts | 4 +- client/src/app/+accounts/accounts.component.scss | 2 +- .../edit-basic-configuration.component.html | 2 +- .../+admin/plugins/shared/plugin-api.service.ts | 4 +- .../my-video-channels.component.html | 7 +- .../my-video-channels.component.scss | 4 + .../my-follows/my-followers.component.html | 31 ++++++ .../my-follows/my-followers.component.scss | 26 ++++++ .../my-follows/my-followers.component.ts | 76 +++++++++++++++ .../my-follows/my-subscriptions.component.html | 36 +++++++ .../my-follows/my-subscriptions.component.scss | 16 ++++ .../my-follows/my-subscriptions.component.ts | 57 +++++++++++ .../app/+my-library/my-library-routing.module.ts | 12 ++- client/src/app/+my-library/my-library.component.ts | 15 ++- client/src/app/+my-library/my-library.module.ts | 6 +- .../my-subscriptions.component.html | 36 ------- .../my-subscriptions.component.scss | 84 ----------------- .../my-subscriptions/my-subscriptions.component.ts | 57 ----------- .../+video-channels/video-channels.component.scss | 2 +- client/src/app/core/rest/rest.service.ts | 16 ++-- .../custom-markup-help.component.html | 2 +- .../advanced-input-filter.component.ts | 2 + .../shared-main/users/user-history.service.ts | 2 +- .../shared-main/users/user-notification.service.ts | 2 +- .../users/user-notifications.component.html | 2 +- .../video-channel/video-channel.service.ts | 2 +- .../app/shared/shared-main/video/video.service.ts | 4 +- .../src/app/shared/shared-search/search.service.ts | 6 +- .../user-subscription.service.ts | 41 +++++++- .../shared-video-comment/video-comment.service.ts | 2 +- .../video-playlist.service.ts | 6 +- client/src/sass/include/_account-channel-page.scss | 88 +++++++++++++++++ client/src/sass/include/_actor.scss | 104 +++++++++------------ client/src/sass/include/_mixins.scss | 9 +- 34 files changed, 486 insertions(+), 279 deletions(-) create mode 100644 client/src/app/+my-library/my-follows/my-followers.component.html create mode 100644 client/src/app/+my-library/my-follows/my-followers.component.scss create mode 100644 client/src/app/+my-library/my-follows/my-followers.component.ts create mode 100644 client/src/app/+my-library/my-follows/my-subscriptions.component.html create mode 100644 client/src/app/+my-library/my-follows/my-subscriptions.component.scss create mode 100644 client/src/app/+my-library/my-follows/my-subscriptions.component.ts delete mode 100644 client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html delete mode 100644 client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss delete mode 100644 client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts create mode 100644 client/src/sass/include/_account-channel-page.scss (limited to 'client/src') diff --git a/client/src/app/+about/about-follows/about-follows.component.ts b/client/src/app/+about/about-follows/about-follows.component.ts index a35272681..84b47e967 100644 --- a/client/src/app/+about/about-follows/about-follows.component.ts +++ b/client/src/app/+about/about-follows/about-follows.component.ts @@ -88,7 +88,7 @@ export class AboutFollowsComponent implements OnInit { } private loadMoreFollowers (reset = false) { - const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination) + const pagination = this.restService.componentToRestPagination(this.followersPagination) this.followService.getFollowers({ pagination, sort: this.sort, state: 'accepted' }) .subscribe({ @@ -106,7 +106,7 @@ export class AboutFollowsComponent implements OnInit { } private loadMoreFollowings (reset = false) { - const pagination = this.restService.componentPaginationToRestPagination(this.followingsPagination) + const pagination = this.restService.componentToRestPagination(this.followingsPagination) this.followService.getFollowing({ pagination, sort: this.sort, state: 'accepted' }) .subscribe({ diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss index c4e2159d1..cdd00487b 100644 --- a/client/src/app/+accounts/accounts.component.scss +++ b/client/src/app/+accounts/accounts.component.scss @@ -1,6 +1,6 @@ @use '_variables' as *; @use '_mixins' as *; -@use '_actor' as *; +@use '_account-channel-page' as *; @use '_miniature' as *; .root { diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html index 1f542e458..537e06d4d 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html @@ -416,7 +416,7 @@

⚠️ This functionality requires a lot of attention and extra moderation.

- See the documentation for more information about the expected URL + See the documentation for more information about the expected URL diff --git a/client/src/app/+admin/plugins/shared/plugin-api.service.ts b/client/src/app/+admin/plugins/shared/plugin-api.service.ts index c4f480cae..b95ee0c9d 100644 --- a/client/src/app/+admin/plugins/shared/plugin-api.service.ts +++ b/client/src/app/+admin/plugins/shared/plugin-api.service.ts @@ -51,7 +51,7 @@ export class PluginApiService { componentPagination: ComponentPagination, sort: string ) { - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) + const pagination = this.restService.componentToRestPagination(componentPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) @@ -67,7 +67,7 @@ export class PluginApiService { sort: string, search?: string ) { - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) + const pagination = this.restService.componentToRestPagination(componentPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html index 4c5b46d5b..bbe583971 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html @@ -27,7 +27,12 @@
{{ videoChannel.nameWithHost }}
-
{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}
+ + {videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}} +
{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss index 9ef5513b6..998e46cb2 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss @@ -54,6 +54,10 @@ my-edit-button { color: $grey-actor-name; } +.video-channel-followers { + color: pvar(--mainForegroundColor); +} + .video-channel-buttons { margin-top: 10px; min-width: 190px; diff --git a/client/src/app/+my-library/my-follows/my-followers.component.html b/client/src/app/+my-library/my-follows/my-followers.component.html new file mode 100644 index 000000000..d2b2dccb6 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-followers.component.html @@ -0,0 +1,31 @@ +

+ + + My followers + {{ pagination.totalItems }} + +

+ +
+ +
+ +
No follower found.
+ +
+
+ + +
+ +
{{ follow.follower.name + '@' + follow.follower.host }}
+ +
+ +
+ Is following all your channels + Is following your channel {{ follow.following.name }} +
+
+
+
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.scss b/client/src/app/+my-library/my-follows/my-followers.component.scss new file mode 100644 index 000000000..15b51c419 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-followers.component.scss @@ -0,0 +1,26 @@ +@use '_variables' as *; +@use '_mixins' as *; +@use '_actor' as *; + +.followers-header { + margin-bottom: 30px; + display: flex; +} + +input[type=text] { + @include peertube-input-text(300px); +} + +.actor { + @include actor-row($avatar-size: 40px, $min-height: auto, $separator: true); + + .actor-display-name { + font-size: 16px; + + + .glyphicon { + @include margin-left(5px); + + font-size: 12px; + } + } +} diff --git a/client/src/app/+my-library/my-follows/my-followers.component.ts b/client/src/app/+my-library/my-follows/my-followers.component.ts new file mode 100644 index 000000000..a7bbe6d99 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-followers.component.ts @@ -0,0 +1,76 @@ +import { Subject } from 'rxjs' +import { Component, OnInit } from '@angular/core' +import { ActivatedRoute } from '@angular/router' +import { AuthService, ComponentPagination, Notifier } from '@app/core' +import { UserSubscriptionService } from '@app/shared/shared-user-subscription' +import { ActorFollow } from '@shared/models' + +@Component({ + templateUrl: './my-followers.component.html', + styleUrls: [ './my-followers.component.scss' ] +}) +export class MyFollowersComponent implements OnInit { + follows: ActorFollow[] = [] + + pagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 10, + totalItems: null + } + + onDataSubject = new Subject() + search: string + + constructor ( + private route: ActivatedRoute, + private auth: AuthService, + private userSubscriptionService: UserSubscriptionService, + private notifier: Notifier + ) {} + + ngOnInit () { + if (this.route.snapshot.queryParams['search']) { + this.search = this.route.snapshot.queryParams['search'] + } + } + + onNearOfBottom () { + // Last page + if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return + + this.pagination.currentPage += 1 + this.loadFollowers() + } + + onSearch (search: string) { + this.search = search + this.loadFollowers(false) + } + + isFollowingAccount (follow: ActorFollow) { + return follow.following.name === this.getUsername() + } + + private loadFollowers (more = true) { + this.userSubscriptionService.listFollowers({ + pagination: this.pagination, + nameWithHost: this.getUsername(), + search: this.search + }).subscribe({ + next: res => { + this.follows = more + ? this.follows.concat(res.data) + : res.data + this.pagination.totalItems = res.total + + this.onDataSubject.next(res.data) + }, + + error: err => this.notifier.error(err.message) + }) + } + + private getUsername () { + return this.auth.getUser().username + } +} diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.html b/client/src/app/+my-library/my-follows/my-subscriptions.component.html new file mode 100644 index 000000000..775f0e783 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.html @@ -0,0 +1,36 @@ +

+ + + My subscriptions + {{ pagination.totalItems }} + +

+ +
+ +
+ +
You don't have any subscription yet.
+ + diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.scss b/client/src/app/+my-library/my-follows/my-subscriptions.component.scss new file mode 100644 index 000000000..310e11cb0 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.scss @@ -0,0 +1,16 @@ +@use '_variables' as *; +@use '_mixins' as *; +@use '_actor' as *; + +.video-subscriptions-header { + margin-bottom: 30px; + display: flex; +} + +input[type=text] { + @include peertube-input-text(300px); +} + +.actor { + @include actor-row; +} diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.ts b/client/src/app/+my-library/my-follows/my-subscriptions.component.ts new file mode 100644 index 000000000..f676aa014 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.ts @@ -0,0 +1,57 @@ +import { Subject } from 'rxjs' +import { Component } from '@angular/core' +import { ComponentPagination, Notifier } from '@app/core' +import { VideoChannel } from '@app/shared/shared-main' +import { UserSubscriptionService } from '@app/shared/shared-user-subscription' + +@Component({ + templateUrl: './my-subscriptions.component.html', + styleUrls: [ './my-subscriptions.component.scss' ] +}) +export class MySubscriptionsComponent { + videoChannels: VideoChannel[] = [] + + pagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 10, + totalItems: null + } + + onDataSubject = new Subject() + + search: string + + constructor ( + private userSubscriptionService: UserSubscriptionService, + private notifier: Notifier + ) {} + + onNearOfBottom () { + // Last page + if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return + + this.pagination.currentPage += 1 + this.loadSubscriptions() + } + + onSearch (search: string) { + this.search = search + this.loadSubscriptions(false) + } + + private loadSubscriptions (more = true) { + this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search }) + .subscribe({ + next: res => { + this.videoChannels = more + ? this.videoChannels.concat(res.data) + : res.data + this.pagination.totalItems = res.total + + this.onDataSubject.next(res.data) + }, + + error: err => this.notifier.error(err.message) + }) + } +} diff --git a/client/src/app/+my-library/my-library-routing.module.ts b/client/src/app/+my-library/my-library-routing.module.ts index 76894bed8..73858fb82 100644 --- a/client/src/app/+my-library/my-library-routing.module.ts +++ b/client/src/app/+my-library/my-library-routing.module.ts @@ -1,10 +1,11 @@ import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' import { LoginGuard } from '../core' +import { MyFollowersComponent } from './my-follows/my-followers.component' +import { MySubscriptionsComponent } from './my-follows/my-subscriptions.component' import { MyHistoryComponent } from './my-history/my-history.component' import { MyLibraryComponent } from './my-library.component' import { MyOwnershipComponent } from './my-ownership/my-ownership.component' -import { MySubscriptionsComponent } from './my-subscriptions/my-subscriptions.component' import { MyVideoImportsComponent } from './my-video-imports/my-video-imports.component' import { MyVideoPlaylistCreateComponent } from './my-video-playlists/my-video-playlist-create.component' import { MyVideoPlaylistElementsComponent } from './my-video-playlists/my-video-playlist-elements.component' @@ -99,6 +100,15 @@ const myLibraryRoutes: Routes = [ } } }, + { + path: 'followers', + component: MyFollowersComponent, + data: { + meta: { + title: $localize`My followers` + } + } + }, { path: 'ownership', component: MyOwnershipComponent, diff --git a/client/src/app/+my-library/my-library.component.ts b/client/src/app/+my-library/my-library.component.ts index 16a7f63e3..ff901952f 100644 --- a/client/src/app/+my-library/my-library.component.ts +++ b/client/src/app/+my-library/my-library.component.ts @@ -61,8 +61,19 @@ export class MyLibraryComponent implements OnInit { }, { - label: $localize`Subscriptions`, - routerLink: '/my-library/subscriptions' + label: $localize`Follows`, + children: [ + { + label: $localize`Subscriptions`, + iconName: 'subscriptions', + routerLink: '/my-library/subscriptions' + }, + { + label: $localize`Followers`, + iconName: 'follower', + routerLink: '/my-library/followers' + } + ] }, { diff --git a/client/src/app/+my-library/my-library.module.ts b/client/src/app/+my-library/my-library.module.ts index 264ad03f7..360c53589 100644 --- a/client/src/app/+my-library/my-library.module.ts +++ b/client/src/app/+my-library/my-library.module.ts @@ -13,12 +13,13 @@ import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscripti import { SharedVideoLiveModule } from '@app/shared/shared-video-live' import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist/shared-video-playlist.module' +import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module' +import { MySubscriptionsComponent } from './my-follows/my-subscriptions.component' import { MyHistoryComponent } from './my-history/my-history.component' import { MyLibraryRoutingModule } from './my-library-routing.module' import { MyLibraryComponent } from './my-library.component' import { MyAcceptOwnershipComponent } from './my-ownership/my-accept-ownership/my-accept-ownership.component' import { MyOwnershipComponent } from './my-ownership/my-ownership.component' -import { MySubscriptionsComponent } from './my-subscriptions/my-subscriptions.component' import { MyVideoImportsComponent } from './my-video-imports/my-video-imports.component' import { MyVideoPlaylistCreateComponent } from './my-video-playlists/my-video-playlist-create.component' import { MyVideoPlaylistElementsComponent } from './my-video-playlists/my-video-playlist-elements.component' @@ -26,7 +27,7 @@ import { MyVideoPlaylistUpdateComponent } from './my-video-playlists/my-video-pl import { MyVideoPlaylistsComponent } from './my-video-playlists/my-video-playlists.component' import { VideoChangeOwnershipComponent } from './my-videos/modals/video-change-ownership.component' import { MyVideosComponent } from './my-videos/my-videos.component' -import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module' +import { MyFollowersComponent } from './my-follows/my-followers.component' @NgModule({ imports: [ @@ -61,6 +62,7 @@ import { SharedActorImageModule } from '../shared/shared-actor-image/shared-acto MyAcceptOwnershipComponent, MyVideoImportsComponent, MySubscriptionsComponent, + MyFollowersComponent, MyHistoryComponent, MyVideoPlaylistCreateComponent, diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html deleted file mode 100644 index ca5ad794a..000000000 --- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html +++ /dev/null @@ -1,36 +0,0 @@ -

- - - My subscriptions - {{ pagination.totalItems }} - -

- -
- -
- -
You don't have any subscription yet.
- - diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss deleted file mode 100644 index edca06a66..000000000 --- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss +++ /dev/null @@ -1,84 +0,0 @@ -@use '_variables' as *; -@use '_mixins' as *; - -input[type=text] { - @include peertube-input-text(300px); -} - -.video-channel { - @include row-blocks; - - > my-actor-avatar { - @include actor-avatar-size(80px); - - @include margin-right(10px); - } -} - -.video-channel-info { - flex-grow: 1; - - a.video-channel-names { - @include disable-default-a-behaviour; - - width: fit-content; - display: flex; - align-items: baseline; - color: pvar(--mainForegroundColor); - - .video-channel-display-name { - font-weight: $font-semibold; - font-size: 18px; - } - - .video-channel-name { - @include margin-left(5px); - - font-size: 14px; - color: $grey-actor-name; - } - } -} - -.actor-owner { - @include disable-default-a-behaviour; - - font-size: 13px; - color: pvar(--mainForegroundColor); - - span:hover { - opacity: 0.8; - } - - my-actor-avatar { - @include margin-left(7px); - display: inline-block; - vertical-align: top; - } -} - -.video-subscriptions-header { - margin-bottom: 30px; - display: flex; -} - -@media screen and (max-width: $small-view) { - .video-subscriptions-header input[type=text] { - width: 100% !important; - } - - .video-channel-info { - padding-bottom: 10px; - text-align: center; - - .video-channel-names { - flex-direction: column; - align-items: center !important; - margin: auto; - } - } - - img { - @include margin-right(0); - } -} diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts deleted file mode 100644 index f676aa014..000000000 --- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Subject } from 'rxjs' -import { Component } from '@angular/core' -import { ComponentPagination, Notifier } from '@app/core' -import { VideoChannel } from '@app/shared/shared-main' -import { UserSubscriptionService } from '@app/shared/shared-user-subscription' - -@Component({ - templateUrl: './my-subscriptions.component.html', - styleUrls: [ './my-subscriptions.component.scss' ] -}) -export class MySubscriptionsComponent { - videoChannels: VideoChannel[] = [] - - pagination: ComponentPagination = { - currentPage: 1, - itemsPerPage: 10, - totalItems: null - } - - onDataSubject = new Subject() - - search: string - - constructor ( - private userSubscriptionService: UserSubscriptionService, - private notifier: Notifier - ) {} - - onNearOfBottom () { - // Last page - if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return - - this.pagination.currentPage += 1 - this.loadSubscriptions() - } - - onSearch (search: string) { - this.search = search - this.loadSubscriptions(false) - } - - private loadSubscriptions (more = true) { - this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search }) - .subscribe({ - next: res => { - this.videoChannels = more - ? this.videoChannels.concat(res.data) - : res.data - this.pagination.totalItems = res.total - - this.onDataSubject.next(res.data) - }, - - error: err => this.notifier.error(err.message) - }) - } -} diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss index d174dcd62..72ee2d7bb 100644 --- a/client/src/app/+video-channels/video-channels.component.scss +++ b/client/src/app/+video-channels/video-channels.component.scss @@ -1,6 +1,6 @@ @use '_variables' as *; @use '_mixins' as *; -@use '_actor' as *; +@use '_account-channel-page' as *; @use '_miniature' as *; .root { diff --git a/client/src/app/core/rest/rest.service.ts b/client/src/app/core/rest/rest.service.ts index 98e45ffc0..93b5f56b2 100644 --- a/client/src/app/core/rest/rest.service.ts +++ b/client/src/app/core/rest/rest.service.ts @@ -13,9 +13,8 @@ interface QueryStringFilterPrefixes { } } -type ParseQueryStringFilterResult = { - [key: string]: string | number | boolean | (string | number | boolean)[] -} +type ParseQueryStringFilters = Partial> +type ParseQueryStringFiltersResult = ParseQueryStringFilters & { search?: string } @Injectable() export class RestService { @@ -67,14 +66,17 @@ export class RestService { return params } - componentPaginationToRestPagination (componentPagination: ComponentPaginationLight): RestPagination { + componentToRestPagination (componentPagination: ComponentPaginationLight): RestPagination { const start: number = (componentPagination.currentPage - 1) * componentPagination.itemsPerPage const count: number = componentPagination.itemsPerPage return { start, count } } - parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): ParseQueryStringFilterResult { + /* + * Returns an object containing the filters and the remaining search + */ + parseQueryStringFilter (q: string, prefixes: T): ParseQueryStringFiltersResult { if (!q) return {} // Tokenize the strings using spaces that are not in quotes @@ -90,9 +92,9 @@ export class RestService { return prefixeStrings.every(prefixString => t.startsWith(prefixString) === false) }) - const additionalFilters: ParseQueryStringFilterResult = {} + const additionalFilters: ParseQueryStringFilters = {} - for (const prefixKey of Object.keys(prefixes)) { + for (const prefixKey of Object.keys(prefixes) as (keyof T)[]) { const prefixObj = prefixes[prefixKey] const prefix = prefixObj.prefix diff --git a/client/src/app/shared/shared-custom-markup/custom-markup-help.component.html b/client/src/app/shared/shared-custom-markup/custom-markup-help.component.html index dd7a56d7d..0ca84ff78 100644 --- a/client/src/app/shared/shared-custom-markup/custom-markup-help.component.html +++ b/client/src/app/shared/shared-custom-markup/custom-markup-help.component.html @@ -1,3 +1,3 @@ - Markdown compatible that also supports custom PeerTube HTML tags + Markdown compatible that also supports custom PeerTube HTML tags diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts index 72cd6d460..113219f48 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts @@ -77,6 +77,8 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { logger('On route search change "%s".', search) + if (this.searchValue === search) return + this.searchValue = search this.emitSearch() }) diff --git a/client/src/app/shared/shared-main/users/user-history.service.ts b/client/src/app/shared/shared-main/users/user-history.service.ts index 91268af8c..a4841897d 100644 --- a/client/src/app/shared/shared-main/users/user-history.service.ts +++ b/client/src/app/shared/shared-main/users/user-history.service.ts @@ -19,7 +19,7 @@ export class UserHistoryService { ) {} getUserVideosHistory (historyPagination: ComponentPaginationLight, search?: string) { - const pagination = this.restService.componentPaginationToRestPagination(historyPagination) + const pagination = this.restService.componentToRestPagination(historyPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination) diff --git a/client/src/app/shared/shared-main/users/user-notification.service.ts b/client/src/app/shared/shared-main/users/user-notification.service.ts index 09fee87a3..e27dab21a 100644 --- a/client/src/app/shared/shared-main/users/user-notification.service.ts +++ b/client/src/app/shared/shared-main/users/user-notification.service.ts @@ -29,7 +29,7 @@ export class UserNotificationService { const { pagination, ignoreLoadingBar, unread, sort } = parameters let params = new HttpParams() - params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination), sort) + params = this.restService.addRestGetParams(params, this.restService.componentToRestPagination(pagination), sort) if (unread) params = params.append('unread', `${unread}`) diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html index ee8df864a..9af6da784 100644 --- a/client/src/app/shared/shared-main/users/user-notifications.component.html +++ b/client/src/app/shared/shared-main/users/user-notifications.component.html @@ -203,7 +203,7 @@
- A new version of PeerTube is available: {{ notification.peertube.latestVersion }} + A new version of PeerTube is available: {{ notification.peertube.latestVersion }}
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts index 7560a35a8..dc00fabdc 100644 --- a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts @@ -50,7 +50,7 @@ export class VideoChannelService { const { account, componentPagination, withStats = false, sort, search } = options const pagination = componentPagination - ? this.restService.componentPaginationToRestPagination(componentPagination) + ? this.restService.componentToRestPagination(componentPagination) : { start: 0, count: 20 } let params = new HttpParams() diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 3481b116f..2f43f1b9d 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts @@ -123,7 +123,7 @@ export class VideoService { } getMyVideos (videoPagination: ComponentPaginationLight, sort: VideoSortField, search?: string): Observable> { - const pagination = this.restService.componentPaginationToRestPagination(videoPagination) + const pagination = this.restService.componentToRestPagination(videoPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) @@ -377,7 +377,7 @@ export class VideoService { private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) { const { params, videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount, nsfwPolicy, isLive, nsfw } = options - const pagination = this.restService.componentPaginationToRestPagination(videoPagination) + const pagination = this.restService.componentToRestPagination(videoPagination) let newParams = this.restService.addRestGetParams(params, pagination, sort) if (filter) newParams = newParams.set('filter', filter) diff --git a/client/src/app/shared/shared-search/search.service.ts b/client/src/app/shared/shared-search/search.service.ts index fdfab0e0e..71350c733 100644 --- a/client/src/app/shared/shared-search/search.service.ts +++ b/client/src/app/shared/shared-search/search.service.ts @@ -43,7 +43,7 @@ export class SearchService { let pagination: RestPagination if (componentPagination) { - pagination = this.restService.componentPaginationToRestPagination(componentPagination) + pagination = this.restService.componentToRestPagination(componentPagination) } let params = new HttpParams() @@ -77,7 +77,7 @@ export class SearchService { let pagination: RestPagination if (componentPagination) { - pagination = this.restService.componentPaginationToRestPagination(componentPagination) + pagination = this.restService.componentToRestPagination(componentPagination) } let params = new HttpParams() @@ -111,7 +111,7 @@ export class SearchService { let pagination: RestPagination if (componentPagination) { - pagination = this.restService.componentPaginationToRestPagination(componentPagination) + pagination = this.restService.componentToRestPagination(componentPagination) } let params = new HttpParams() diff --git a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts index f289fb6cf..ede65ff39 100644 --- a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts +++ b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts @@ -6,7 +6,7 @@ import { Injectable } from '@angular/core' import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' import { buildBulkObservable } from '@app/helpers' import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' -import { ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models' +import { ActorFollow, ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models' import { environment } from '../../../environments/environment' const logger = debug('peertube:subscriptions:UserSubscriptionService') @@ -17,6 +17,8 @@ type SubscriptionExistResultObservable = { [ uri: string ]: Observable @Injectable() export class UserSubscriptionService { static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/users/me/subscriptions' + static BASE_VIDEO_CHANNELS_URL = environment.apiUrl + '/api/v1/video-channels' + static BASE_ACCOUNTS_URL = environment.apiUrl + '/api/v1/accounts' // Use a replay subject because we "next" a value before subscribing private existsSubject = new ReplaySubject(1) @@ -43,13 +45,46 @@ export class UserSubscriptionService { ) } + listFollowers (parameters: { + pagination: ComponentPaginationLight + nameWithHost: string + search?: string + }) { + const { pagination, nameWithHost, search } = parameters + + let url = `${UserSubscriptionService.BASE_ACCOUNTS_URL}/${nameWithHost}/followers` + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, this.restService.componentToRestPagination(pagination), '-createdAt') + + if (search) { + const filters = this.restService.parseQueryStringFilter(search, { + channel: { + prefix: 'channel:' + } + }) + + if (filters.channel) { + url = `${UserSubscriptionService.BASE_VIDEO_CHANNELS_URL}/${filters.channel}/followers` + } + + params = this.restService.addObjectParams(params, { search: filters.search }) + } + + return this.authHttp + .get>(url, { params }) + .pipe( + catchError(err => this.restExtractor.handleError(err)) + ) + } + getUserSubscriptionVideos (parameters: { videoPagination: ComponentPaginationLight sort: VideoSortField skipCount?: boolean }): Observable> { const { videoPagination, sort, skipCount } = parameters - const pagination = this.restService.componentPaginationToRestPagination(videoPagination) + const pagination = this.restService.componentToRestPagination(videoPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) @@ -106,7 +141,7 @@ export class UserSubscriptionService { const { pagination, search } = parameters const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL - const restPagination = this.restService.componentPaginationToRestPagination(pagination) + const restPagination = this.restService.componentToRestPagination(pagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, restPagination) diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts index 5550c96e4..fd1cae7f8 100644 --- a/client/src/app/shared/shared-video-comment/video-comment.service.ts +++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts @@ -81,7 +81,7 @@ export class VideoCommentService { }): Observable> { const { videoId, componentPagination, sort } = parameters - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) + const pagination = this.restService.componentToRestPagination(componentPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts index fc291329a..3faf81d11 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts @@ -62,7 +62,7 @@ export class VideoPlaylistService { listChannelPlaylists (videoChannel: VideoChannel, componentPagination: ComponentPaginationLight): Observable> { const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists' - const pagination = this.restService.componentPaginationToRestPagination(componentPagination) + const pagination = this.restService.componentToRestPagination(componentPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination) @@ -103,7 +103,7 @@ export class VideoPlaylistService { ): Observable> { const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists' const pagination = componentPagination - ? this.restService.componentPaginationToRestPagination(componentPagination) + ? this.restService.componentToRestPagination(componentPagination) : undefined let params = new HttpParams() @@ -259,7 +259,7 @@ export class VideoPlaylistService { componentPagination: ComponentPaginationLight }): Observable> { const path = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + options.videoPlaylistId + '/videos' - const pagination = this.restService.componentPaginationToRestPagination(options.componentPagination) + const pagination = this.restService.componentToRestPagination(options.componentPagination) let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination) diff --git a/client/src/sass/include/_account-channel-page.scss b/client/src/sass/include/_account-channel-page.scss new file mode 100644 index 000000000..b135bbb6d --- /dev/null +++ b/client/src/sass/include/_account-channel-page.scss @@ -0,0 +1,88 @@ +@use '_variables' as *; +@use '_mixins' as *; + +@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; +} + +@mixin avatar-row-responsive ($img-margin, $grey-font-size) { + display: flex; + grid-column: 1; + margin-bottom: 30px; + + .main-avatar { + @include actor-avatar-size(120px); + } + + > div { + @include margin-left($img-margin); + + min-width: 1px; + } + + .actor-info { + display: flex; + + > div:first-child { + flex-grow: 1; + min-width: 1px; + } + } + + .actor-display-name { + @include peertube-word-wrap; + + display: flex; + flex-wrap: wrap; + } + + h1 { + font-size: 28px; + font-weight: $font-bold; + margin: 0; + } + + .actor-handle { + @include ellipsis; + } + + .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; + } + + .main-avatar { + @include actor-avatar-size(80px); + } + } +} diff --git a/client/src/sass/include/_actor.scss b/client/src/sass/include/_actor.scss index b135bbb6d..f9e44b8ad 100644 --- a/client/src/sass/include/_actor.scss +++ b/client/src/sass/include/_actor.scss @@ -1,88 +1,68 @@ @use '_variables' as *; @use '_mixins' as *; -@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; -} - -@mixin avatar-row-responsive ($img-margin, $grey-font-size) { - display: flex; - grid-column: 1; - margin-bottom: 30px; +@mixin actor-row ($avatar-size: 80px, $avatar-margin-right: 10px, $min-height: 130px, $separator: true) { + @include row-blocks($min-height: $min-height, $separator: $separator); - .main-avatar { - @include actor-avatar-size(120px); - } - - > div { - @include margin-left($img-margin); + > my-actor-avatar { + @include actor-avatar-size($avatar-size); - min-width: 1px; + @include margin-right($avatar-margin-right); } .actor-info { - display: flex; - - > div:first-child { - flex-grow: 1; - min-width: 1px; - } + flex-grow: 1; } - .actor-display-name { - @include peertube-word-wrap; + .actor-names { + @include disable-default-a-behaviour; + width: fit-content; display: flex; - flex-wrap: wrap; + align-items: baseline; + color: pvar(--mainForegroundColor); } - h1 { - font-size: 28px; - font-weight: $font-bold; - margin: 0; + .actor-display-name { + font-weight: $font-semibold; + font-size: 18px; } - .actor-handle { - @include ellipsis; - } + .actor-name { + @include margin-left(5px); - .actor-handle, - .actor-counters { - color: pvar(--greyForegroundColor); - font-size: $grey-font-size; + font-size: 14px; + color: $grey-actor-name; } - .actor-counters > *:not(:last-child)::after { - content: '•'; - margin: 0 10px; - color: pvar(--mainColor); - } + .actor-owner { + @include disable-default-a-behaviour; - @media screen and (max-width: $mobile-view) { - margin-bottom: 15px; + font-size: 13px; + color: pvar(--mainForegroundColor); - h1 { - font-size: 22px; + span:hover { + opacity: 0.8; } - .main-avatar { - @include actor-avatar-size(80px); + my-actor-avatar { + @include margin-left(7px); + + display: inline-block; + vertical-align: top; + } + } + + @media screen and (max-width: $small-view) { + .actor-info { + padding-bottom: 10px; + text-align: center; + + .actor-names { + flex-direction: column; + align-items: center !important; + margin: auto; + } } } } diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 9e510771e..2f436d787 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -653,12 +653,15 @@ @include button-with-icon(20px, 5px, -1px); } -@mixin row-blocks ($column-responsive: true) { +@mixin row-blocks ($column-responsive: true, $min-height: 130px, $separator: true) { display: flex; - min-height: 130px; + min-height: $min-height; padding-bottom: 20px; margin-bottom: 20px; - border-bottom: 1px solid #C6C6C6; + + @if $separator { + border-bottom: 1px solid #C6C6C6; + } @media screen and (max-width: $small-view) { @if $column-responsive { -- cgit v1.2.3