diff options
42 files changed, 647 insertions, 98 deletions
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 c9c7fa8eb..39c0840e4 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 | |||
@@ -2,7 +2,7 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .row { | 4 | .row { |
5 | text-align: center; | 5 | justify-content: center; |
6 | } | 6 | } |
7 | 7 | ||
8 | a.video-channel { | 8 | a.video-channel { |
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts index 6f0806e8a..c1c979151 100644 --- a/client/src/app/+my-account/my-account-routing.module.ts +++ b/client/src/app/+my-account/my-account-routing.module.ts | |||
@@ -9,6 +9,7 @@ import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-vid | |||
9 | import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' | 9 | import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' |
10 | import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' | 10 | import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' |
11 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' | 11 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' |
12 | import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component' | ||
12 | 13 | ||
13 | const myAccountRoutes: Routes = [ | 14 | const myAccountRoutes: Routes = [ |
14 | { | 15 | { |
@@ -74,6 +75,15 @@ const myAccountRoutes: Routes = [ | |||
74 | title: 'Account video imports' | 75 | title: 'Account video imports' |
75 | } | 76 | } |
76 | } | 77 | } |
78 | }, | ||
79 | { | ||
80 | path: 'subscriptions', | ||
81 | component: MyAccountSubscriptionsComponent, | ||
82 | data: { | ||
83 | meta: { | ||
84 | title: 'Account subscriptions' | ||
85 | } | ||
86 | } | ||
77 | } | 87 | } |
78 | ] | 88 | ] |
79 | } | 89 | } |
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html new file mode 100644 index 000000000..4c68cd1a5 --- /dev/null +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html | |||
@@ -0,0 +1,23 @@ | |||
1 | <div class="video-channels"> | ||
2 | <div *ngFor="let videoChannel of videoChannels" class="video-channel"> | ||
3 | <a [routerLink]="[ '/video-channels', videoChannel.name ]"> | ||
4 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | ||
5 | </a> | ||
6 | |||
7 | <div class="video-channel-info"> | ||
8 | <a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names" i18n-title title="Go to the channel"> | ||
9 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> | ||
10 | <div class="video-channel-name">{{ videoChannel.name }}</div> | ||
11 | </a> | ||
12 | |||
13 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> | ||
14 | |||
15 | <a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Go the owner account page" class="actor-owner"> | ||
16 | <span i18n>Created by {{ videoChannel.ownerBy }}</span> | ||
17 | <img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" /> | ||
18 | </a> | ||
19 | </div> | ||
20 | |||
21 | <my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button> | ||
22 | </div> | ||
23 | </div> | ||
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss new file mode 100644 index 000000000..2fbfa335b --- /dev/null +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss | |||
@@ -0,0 +1,49 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .video-channel { | ||
5 | @include row-blocks; | ||
6 | |||
7 | img { | ||
8 | @include avatar(80px); | ||
9 | |||
10 | margin-right: 10px; | ||
11 | } | ||
12 | |||
13 | .video-channel-info { | ||
14 | flex-grow: 1; | ||
15 | |||
16 | a.video-channel-names { | ||
17 | @include disable-default-a-behaviour; | ||
18 | |||
19 | width: fit-content; | ||
20 | display: flex; | ||
21 | align-items: baseline; | ||
22 | color: #000; | ||
23 | |||
24 | .video-channel-display-name { | ||
25 | font-weight: $font-semibold; | ||
26 | font-size: 18px; | ||
27 | } | ||
28 | |||
29 | .video-channel-name { | ||
30 | font-size: 14px; | ||
31 | color: $grey-actor-name; | ||
32 | margin-left: 5px; | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | |||
37 | .actor-owner { | ||
38 | @include actor-owner; | ||
39 | } | ||
40 | |||
41 | my-subscribe-button { | ||
42 | /deep/ span[role=button] { | ||
43 | padding: 7px 12px; | ||
44 | font-size: 16px; | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | |||
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts new file mode 100644 index 000000000..1e94cf90b --- /dev/null +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts | |||
@@ -0,0 +1,30 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { NotificationsService } from 'angular2-notifications' | ||
3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { UserSubscriptionService } from '@app/shared/user-subscription' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-account-subscriptions', | ||
9 | templateUrl: './my-account-subscriptions.component.html', | ||
10 | styleUrls: [ './my-account-subscriptions.component.scss' ] | ||
11 | }) | ||
12 | export class MyAccountSubscriptionsComponent implements OnInit { | ||
13 | videoChannels: VideoChannel[] = [] | ||
14 | |||
15 | constructor ( | ||
16 | private userSubscriptionService: UserSubscriptionService, | ||
17 | private notificationsService: NotificationsService, | ||
18 | private i18n: I18n | ||
19 | ) {} | ||
20 | |||
21 | ngOnInit () { | ||
22 | this.userSubscriptionService.listSubscriptions() | ||
23 | .subscribe( | ||
24 | res => { console.log(res); this.videoChannels = res.data }, | ||
25 | |||
26 | error => this.notificationsService.error(this.i18n('Error'), error.message) | ||
27 | ) | ||
28 | } | ||
29 | |||
30 | } | ||
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts index e25037e24..56697030b 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts | |||
@@ -78,7 +78,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
78 | support: body.support || null | 78 | support: body.support || null |
79 | } | 79 | } |
80 | 80 | ||
81 | this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe( | 81 | this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe( |
82 | () => { | 82 | () => { |
83 | this.authService.refreshUserInformation() | 83 | this.authService.refreshUserInformation() |
84 | this.notificationsService.success( | 84 | this.notificationsService.success( |
@@ -93,7 +93,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE | |||
93 | } | 93 | } |
94 | 94 | ||
95 | onAvatarChange (formData: FormData) { | 95 | onAvatarChange (formData: FormData) { |
96 | this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.uuid, formData) | 96 | this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData) |
97 | .subscribe( | 97 | .subscribe( |
98 | data => { | 98 | data => { |
99 | this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) | 99 | this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss index f8fd2684e..5c892be01 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss | |||
@@ -12,11 +12,7 @@ | |||
12 | } | 12 | } |
13 | 13 | ||
14 | .video-channel { | 14 | .video-channel { |
15 | display: flex; | 15 | @include row-blocks; |
16 | min-height: 130px; | ||
17 | padding-bottom: 20px; | ||
18 | margin-bottom: 20px; | ||
19 | border-bottom: 1px solid #C6C6C6; | ||
20 | 16 | ||
21 | img { | 17 | img { |
22 | @include avatar(80px); | 18 | @include avatar(80px); |
@@ -42,7 +38,7 @@ | |||
42 | 38 | ||
43 | .video-channel-name { | 39 | .video-channel-name { |
44 | font-size: 14px; | 40 | font-size: 14px; |
45 | color: #777272; | 41 | color: $grey-actor-name; |
46 | margin-left: 5px; | 42 | margin-left: 5px; |
47 | } | 43 | } |
48 | } | 44 | } |
@@ -64,12 +60,9 @@ | |||
64 | } | 60 | } |
65 | 61 | ||
66 | .video-channel { | 62 | .video-channel { |
67 | flex-direction: column; | ||
68 | height: auto; | ||
69 | text-align: center; | ||
70 | |||
71 | .video-channel-names { | 63 | .video-channel-names { |
72 | justify-content: center; | 64 | flex-direction: column; |
65 | align-items: center !important; | ||
73 | } | 66 | } |
74 | 67 | ||
75 | img { | 68 | img { |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss index 64a04fa20..cd805be73 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss | |||
@@ -42,11 +42,7 @@ | |||
42 | } | 42 | } |
43 | 43 | ||
44 | .video { | 44 | .video { |
45 | display: flex; | 45 | @include row-blocks; |
46 | min-height: 130px; | ||
47 | padding-bottom: 20px; | ||
48 | margin-bottom: 20px; | ||
49 | border-bottom: 1px solid #C6C6C6; | ||
50 | 46 | ||
51 | &:first-child { | 47 | &:first-child { |
52 | margin-top: 47px; | 48 | margin-top: 47px; |
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html index ddb0570db..74742649c 100644 --- a/client/src/app/+my-account/my-account.component.html +++ b/client/src/app/+my-account/my-account.component.html | |||
@@ -2,11 +2,13 @@ | |||
2 | <div class="sub-menu"> | 2 | <div class="sub-menu"> |
3 | <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a> | 3 | <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a> |
4 | 4 | ||
5 | <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a> | 5 | <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My channels</a> |
6 | 6 | ||
7 | <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a> | 7 | <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a> |
8 | 8 | ||
9 | <a *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My video imports</a> | 9 | <a i18n routerLink="/my-account/subscriptions" routerLinkActive="active" class="title-page">My subscriptions</a> |
10 | |||
11 | <a *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My imports</a> | ||
10 | </div> | 12 | </div> |
11 | 13 | ||
12 | <div class="margin-content"> | 14 | <div class="margin-content"> |
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 29b49e8d9..c93f38d4b 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -14,6 +14,7 @@ import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-accoun | |||
14 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' | 14 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' |
15 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' | 15 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' |
16 | import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' | 16 | import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' |
17 | import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component' | ||
17 | 18 | ||
18 | @NgModule({ | 19 | @NgModule({ |
19 | imports: [ | 20 | imports: [ |
@@ -34,7 +35,8 @@ import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settin | |||
34 | MyAccountVideoChannelUpdateComponent, | 35 | MyAccountVideoChannelUpdateComponent, |
35 | ActorAvatarInfoComponent, | 36 | ActorAvatarInfoComponent, |
36 | MyAccountVideoImportsComponent, | 37 | MyAccountVideoImportsComponent, |
37 | MyAccountDangerZoneComponent | 38 | MyAccountDangerZoneComponent, |
39 | MyAccountSubscriptionsComponent | ||
38 | ], | 40 | ], |
39 | 41 | ||
40 | exports: [ | 42 | exports: [ |
diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.scss b/client/src/app/+my-account/shared/actor-avatar-info.component.scss index 36a792f82..0b0c83de5 100644 --- a/client/src/app/+my-account/shared/actor-avatar-info.component.scss +++ b/client/src/app/+my-account/shared/actor-avatar-info.component.scss | |||
@@ -25,7 +25,7 @@ | |||
25 | position: relative; | 25 | position: relative; |
26 | top: 2px; | 26 | top: 2px; |
27 | font-size: 14px; | 27 | font-size: 14px; |
28 | color: #777272; | 28 | color: $grey-actor-name; |
29 | } | 29 | } |
30 | } | 30 | } |
31 | 31 | ||
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html index 5a69a82a0..1941a2eab 100644 --- a/client/src/app/+video-channels/video-channels.component.html +++ b/client/src/app/+video-channels/video-channels.component.html | |||
@@ -8,6 +8,8 @@ | |||
8 | <div class="actor-names"> | 8 | <div class="actor-names"> |
9 | <div class="actor-display-name">{{ videoChannel.displayName }}</div> | 9 | <div class="actor-display-name">{{ videoChannel.displayName }}</div> |
10 | <div class="actor-name">{{ videoChannel.nameWithHost }}</div> | 10 | <div class="actor-name">{{ videoChannel.nameWithHost }}</div> |
11 | |||
12 | <my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button> | ||
11 | </div> | 13 | </div> |
12 | <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div> | 14 | <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div> |
13 | 15 | ||
@@ -20,7 +22,6 @@ | |||
20 | 22 | ||
21 | <div class="links"> | 23 | <div class="links"> |
22 | <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> | 24 | <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> |
23 | |||
24 | <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> | 25 | <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> |
25 | </div> | 26 | </div> |
26 | </div> | 27 | </div> |
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss index 909b65bc7..a63b1ec06 100644 --- a/client/src/app/+video-channels/video-channels.component.scss +++ b/client/src/app/+video-channels/video-channels.component.scss | |||
@@ -3,4 +3,19 @@ | |||
3 | 3 | ||
4 | .sub-menu { | 4 | .sub-menu { |
5 | @include sub-menu-with-actor; | 5 | @include sub-menu-with-actor; |
6 | |||
7 | .actor, .actor-info { | ||
8 | width: 100%; | ||
9 | } | ||
10 | |||
11 | .actor-name { | ||
12 | flex-grow: 1; | ||
13 | } | ||
14 | |||
15 | my-subscribe-button { | ||
16 | /deep/ span[role=button] { | ||
17 | padding: 7px 12px; | ||
18 | font-size: 16px; | ||
19 | } | ||
20 | } | ||
6 | } \ No newline at end of file | 21 | } \ No newline at end of file |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 7edcdf501..bd03af9b3 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -42,6 +42,11 @@ | |||
42 | <div class="panel-block"> | 42 | <div class="panel-block"> |
43 | <div i18n class="block-title">Videos</div> | 43 | <div i18n class="block-title">Videos</div> |
44 | 44 | ||
45 | <a *ngIf="isLoggedIn" routerLink="/videos/subscriptions" routerLinkActive="active"> | ||
46 | <span class="icon icon-videos-subscriptions"></span> | ||
47 | <ng-container i18n>Subscriptions</ng-container> | ||
48 | </a> | ||
49 | |||
45 | <a routerLink="/videos/trending" routerLinkActive="active"> | 50 | <a routerLink="/videos/trending" routerLinkActive="active"> |
46 | <span class="icon icon-videos-trending"></span> | 51 | <span class="icon icon-videos-trending"></span> |
47 | <ng-container i18n>Trending</ng-container> | 52 | <ng-container i18n>Trending</ng-container> |
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 39f1e9be0..606fea961 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -135,6 +135,12 @@ menu { | |||
135 | 135 | ||
136 | margin-right: 18px; | 136 | margin-right: 18px; |
137 | 137 | ||
138 | &.icon-videos-subscriptions { | ||
139 | position: relative; | ||
140 | top: -2px; | ||
141 | background-image: url('../../assets/images/menu/subscriptions.svg'); | ||
142 | } | ||
143 | |||
138 | &.icon-videos-trending { | 144 | &.icon-videos-trending { |
139 | position: relative; | 145 | position: relative; |
140 | top: -2px; | 146 | top: -2px; |
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 722415a06..9bc7ad88b 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -36,7 +36,8 @@ import { | |||
36 | ReactiveFileComponent, | 36 | ReactiveFileComponent, |
37 | ResetPasswordValidatorsService, | 37 | ResetPasswordValidatorsService, |
38 | UserValidatorsService, | 38 | UserValidatorsService, |
39 | VideoAbuseValidatorsService, VideoBlacklistValidatorsService, | 39 | VideoAbuseValidatorsService, |
40 | VideoBlacklistValidatorsService, | ||
40 | VideoChannelValidatorsService, | 41 | VideoChannelValidatorsService, |
41 | VideoCommentValidatorsService, | 42 | VideoCommentValidatorsService, |
42 | VideoValidatorsService | 43 | VideoValidatorsService |
@@ -49,6 +50,7 @@ import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.c | |||
49 | import { VideoImportService } from '@app/shared/video-import/video-import.service' | 50 | import { VideoImportService } from '@app/shared/video-import/video-import.service' |
50 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' | 51 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' |
51 | import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 52 | import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
53 | import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' | ||
52 | 54 | ||
53 | @NgModule({ | 55 | @NgModule({ |
54 | imports: [ | 56 | imports: [ |
@@ -83,7 +85,8 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N | |||
83 | InfiniteScrollerDirective, | 85 | InfiniteScrollerDirective, |
84 | HelpComponent, | 86 | HelpComponent, |
85 | ReactiveFileComponent, | 87 | ReactiveFileComponent, |
86 | PeertubeCheckboxComponent | 88 | PeertubeCheckboxComponent, |
89 | SubscribeButtonComponent | ||
87 | ], | 90 | ], |
88 | 91 | ||
89 | exports: [ | 92 | exports: [ |
@@ -115,6 +118,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N | |||
115 | HelpComponent, | 118 | HelpComponent, |
116 | ReactiveFileComponent, | 119 | ReactiveFileComponent, |
117 | PeertubeCheckboxComponent, | 120 | PeertubeCheckboxComponent, |
121 | SubscribeButtonComponent, | ||
118 | 122 | ||
119 | NumberFormatterPipe, | 123 | NumberFormatterPipe, |
120 | ObjectLengthPipe, | 124 | ObjectLengthPipe, |
@@ -134,6 +138,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N | |||
134 | VideoChannelService, | 138 | VideoChannelService, |
135 | VideoCaptionService, | 139 | VideoCaptionService, |
136 | VideoImportService, | 140 | VideoImportService, |
141 | UserSubscriptionService, | ||
137 | 142 | ||
138 | FormValidatorService, | 143 | FormValidatorService, |
139 | CustomConfigValidatorsService, | 144 | CustomConfigValidatorsService, |
diff --git a/client/src/app/shared/user-subscription/index.ts b/client/src/app/shared/user-subscription/index.ts new file mode 100644 index 000000000..024b36a41 --- /dev/null +++ b/client/src/app/shared/user-subscription/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './user-subscription.service' | ||
2 | export * from './subscribe-button.component' \ No newline at end of file | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.html b/client/src/app/shared/user-subscription/subscribe-button.component.html new file mode 100644 index 000000000..63b313662 --- /dev/null +++ b/client/src/app/shared/user-subscription/subscribe-button.component.html | |||
@@ -0,0 +1,15 @@ | |||
1 | <span i18n *ngIf="subscribed === false" class="subscribe-button" role="button" (click)="subscribe()"> | ||
2 | <span>Subscribe</span> | ||
3 | <span *ngIf="displayFollowers && videoChannel.followersCount !== 0" class="followers-count"> | ||
4 | {{ videoChannel.followersCount | myNumberFormatter }} | ||
5 | </span> | ||
6 | </span> | ||
7 | |||
8 | <span *ngIf="subscribed === true" class="unsubscribe-button" role="button" (click)="unsubscribe()"> | ||
9 | <span class="subscribed" i18n>Subscribed</span> | ||
10 | <span class="unsubscribe" i18n>Unsubscribe</span> | ||
11 | |||
12 | <span *ngIf="displayFollowers && videoChannel.followersCount !== 0" class="followers-count"> | ||
13 | {{ videoChannel.followersCount | myNumberFormatter }} | ||
14 | </span> | ||
15 | </span> | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.scss b/client/src/app/shared/user-subscription/subscribe-button.component.scss new file mode 100644 index 000000000..9811fdc0c --- /dev/null +++ b/client/src/app/shared/user-subscription/subscribe-button.component.scss | |||
@@ -0,0 +1,37 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .subscribe-button { | ||
5 | @include peertube-button; | ||
6 | @include orange-button; | ||
7 | } | ||
8 | |||
9 | .unsubscribe-button { | ||
10 | @include peertube-button; | ||
11 | @include grey-button | ||
12 | } | ||
13 | |||
14 | .subscribe-button, | ||
15 | .unsubscribe-button { | ||
16 | padding: 3px 7px; | ||
17 | } | ||
18 | |||
19 | .unsubscribe-button { | ||
20 | .subscribed { | ||
21 | display: inline; | ||
22 | } | ||
23 | |||
24 | .unsubscribe { | ||
25 | display: none; | ||
26 | } | ||
27 | |||
28 | &:hover { | ||
29 | .subscribed { | ||
30 | display: none; | ||
31 | } | ||
32 | |||
33 | .unsubscribe { | ||
34 | display: inline; | ||
35 | } | ||
36 | } | ||
37 | } \ No newline at end of file | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.ts b/client/src/app/shared/user-subscription/subscribe-button.component.ts new file mode 100644 index 000000000..46d6dbaf7 --- /dev/null +++ b/client/src/app/shared/user-subscription/subscribe-button.component.ts | |||
@@ -0,0 +1,74 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { AuthService } from '@app/core' | ||
3 | import { RestExtractor } from '@app/shared/rest' | ||
4 | import { RedirectService } from '@app/core/routing/redirect.service' | ||
5 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | ||
6 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
7 | import { NotificationsService } from 'angular2-notifications' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-subscribe-button', | ||
12 | templateUrl: './subscribe-button.component.html', | ||
13 | styleUrls: [ './subscribe-button.component.scss' ] | ||
14 | }) | ||
15 | export class SubscribeButtonComponent implements OnInit { | ||
16 | @Input() videoChannel: VideoChannel | ||
17 | @Input() displayFollowers = false | ||
18 | |||
19 | subscribed: boolean | ||
20 | |||
21 | constructor ( | ||
22 | private authService: AuthService, | ||
23 | private restExtractor: RestExtractor, | ||
24 | private redirectService: RedirectService, | ||
25 | private notificationsService: NotificationsService, | ||
26 | private userSubscriptionService: UserSubscriptionService, | ||
27 | private i18n: I18n | ||
28 | ) { } | ||
29 | |||
30 | get uri () { | ||
31 | return this.videoChannel.name + '@' + this.videoChannel.host | ||
32 | } | ||
33 | |||
34 | ngOnInit () { | ||
35 | this.userSubscriptionService.isSubscriptionExists(this.uri) | ||
36 | .subscribe( | ||
37 | exists => this.subscribed = exists, | ||
38 | |||
39 | err => this.notificationsService.error(this.i18n('Error'), err.message) | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | subscribe () { | ||
44 | this.userSubscriptionService.addSubscription(this.uri) | ||
45 | .subscribe( | ||
46 | () => { | ||
47 | this.subscribed = true | ||
48 | |||
49 | this.notificationsService.success( | ||
50 | this.i18n('Subscribed'), | ||
51 | this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }) | ||
52 | ) | ||
53 | }, | ||
54 | |||
55 | err => this.notificationsService.error(this.i18n('Error'), err.message) | ||
56 | ) | ||
57 | } | ||
58 | |||
59 | unsubscribe () { | ||
60 | this.userSubscriptionService.deleteSubscription(this.uri) | ||
61 | .subscribe( | ||
62 | () => { | ||
63 | this.subscribed = false | ||
64 | |||
65 | this.notificationsService.success( | ||
66 | this.i18n('Unsubscribed'), | ||
67 | this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }) | ||
68 | ) | ||
69 | }, | ||
70 | |||
71 | err => this.notificationsService.error(this.i18n('Error'), err.message) | ||
72 | ) | ||
73 | } | ||
74 | } | ||
diff --git a/client/src/app/shared/user-subscription/user-subscription.service.ts b/client/src/app/shared/user-subscription/user-subscription.service.ts new file mode 100644 index 000000000..3103706d1 --- /dev/null +++ b/client/src/app/shared/user-subscription/user-subscription.service.ts | |||
@@ -0,0 +1,66 @@ | |||
1 | import { catchError, map } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { ResultList } from '../../../../../shared' | ||
5 | import { environment } from '../../../environments/environment' | ||
6 | import { RestExtractor } from '../rest' | ||
7 | import { Observable, of } from 'rxjs' | ||
8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
10 | import { VideoChannel as VideoChannelServer } from '../../../../../shared/models/videos' | ||
11 | |||
12 | @Injectable() | ||
13 | export class UserSubscriptionService { | ||
14 | static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/users/me/subscriptions' | ||
15 | |||
16 | constructor ( | ||
17 | private authHttp: HttpClient, | ||
18 | private restExtractor: RestExtractor | ||
19 | ) { | ||
20 | } | ||
21 | |||
22 | deleteSubscription (nameWithHost: string) { | ||
23 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost | ||
24 | |||
25 | return this.authHttp.delete(url) | ||
26 | .pipe( | ||
27 | map(this.restExtractor.extractDataBool), | ||
28 | catchError(err => this.restExtractor.handleError(err)) | ||
29 | ) | ||
30 | } | ||
31 | |||
32 | addSubscription (nameWithHost: string) { | ||
33 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL | ||
34 | |||
35 | const body = { uri: nameWithHost } | ||
36 | return this.authHttp.post(url, body) | ||
37 | .pipe( | ||
38 | map(this.restExtractor.extractDataBool), | ||
39 | catchError(err => this.restExtractor.handleError(err)) | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | listSubscriptions (): Observable<ResultList<VideoChannel>> { | ||
44 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL | ||
45 | |||
46 | return this.authHttp.get<ResultList<VideoChannelServer>>(url) | ||
47 | .pipe( | ||
48 | map(res => VideoChannelService.extractVideoChannels(res)), | ||
49 | catchError(err => this.restExtractor.handleError(err)) | ||
50 | ) | ||
51 | } | ||
52 | |||
53 | isSubscriptionExists (nameWithHost: string): Observable<boolean> { | ||
54 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost | ||
55 | |||
56 | return this.authHttp.get(url) | ||
57 | .pipe( | ||
58 | map(this.restExtractor.extractDataBool), | ||
59 | catchError(err => { | ||
60 | if (err.status === 404) return of(false) | ||
61 | |||
62 | return this.restExtractor.handleError(err) | ||
63 | }) | ||
64 | ) | ||
65 | } | ||
66 | } | ||
diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/video-channel/video-channel.service.ts index 510dc9c3d..46b121790 100644 --- a/client/src/app/shared/video-channel/video-channel.service.ts +++ b/client/src/app/shared/video-channel/video-channel.service.ts | |||
@@ -22,6 +22,16 @@ export class VideoChannelService { | |||
22 | private restExtractor: RestExtractor | 22 | private restExtractor: RestExtractor |
23 | ) {} | 23 | ) {} |
24 | 24 | ||
25 | static extractVideoChannels (result: ResultList<VideoChannelServer>) { | ||
26 | const videoChannels: VideoChannel[] = [] | ||
27 | |||
28 | for (const videoChannelJSON of result.data) { | ||
29 | videoChannels.push(new VideoChannel(videoChannelJSON)) | ||
30 | } | ||
31 | |||
32 | return { data: videoChannels, total: result.total } | ||
33 | } | ||
34 | |||
25 | getVideoChannel (videoChannelName: string) { | 35 | getVideoChannel (videoChannelName: string) { |
26 | return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName) | 36 | return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName) |
27 | .pipe( | 37 | .pipe( |
@@ -34,7 +44,7 @@ export class VideoChannelService { | |||
34 | listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> { | 44 | listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> { |
35 | return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') | 45 | return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') |
36 | .pipe( | 46 | .pipe( |
37 | map(res => this.extractVideoChannels(res)), | 47 | map(res => VideoChannelService.extractVideoChannels(res)), |
38 | catchError(err => this.restExtractor.handleError(err)) | 48 | catchError(err => this.restExtractor.handleError(err)) |
39 | ) | 49 | ) |
40 | } | 50 | } |
@@ -47,16 +57,16 @@ export class VideoChannelService { | |||
47 | ) | 57 | ) |
48 | } | 58 | } |
49 | 59 | ||
50 | updateVideoChannel (videoChannelUUID: string, videoChannel: VideoChannelUpdate) { | 60 | updateVideoChannel (videoChannelName: string, videoChannel: VideoChannelUpdate) { |
51 | return this.authHttp.put(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID, videoChannel) | 61 | return this.authHttp.put(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName, videoChannel) |
52 | .pipe( | 62 | .pipe( |
53 | map(this.restExtractor.extractDataBool), | 63 | map(this.restExtractor.extractDataBool), |
54 | catchError(err => this.restExtractor.handleError(err)) | 64 | catchError(err => this.restExtractor.handleError(err)) |
55 | ) | 65 | ) |
56 | } | 66 | } |
57 | 67 | ||
58 | changeVideoChannelAvatar (videoChannelUUID: string, avatarForm: FormData) { | 68 | changeVideoChannelAvatar (videoChannelName: string, avatarForm: FormData) { |
59 | const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID + '/avatar/pick' | 69 | const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar/pick' |
60 | 70 | ||
61 | return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) | 71 | return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) |
62 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 72 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
@@ -69,14 +79,4 @@ export class VideoChannelService { | |||
69 | catchError(err => this.restExtractor.handleError(err)) | 79 | catchError(err => this.restExtractor.handleError(err)) |
70 | ) | 80 | ) |
71 | } | 81 | } |
72 | |||
73 | private extractVideoChannels (result: ResultList<VideoChannelServer>) { | ||
74 | const videoChannels: VideoChannel[] = [] | ||
75 | |||
76 | for (const videoChannelJSON of result.data) { | ||
77 | videoChannels.push(new VideoChannel(videoChannelJSON)) | ||
78 | } | ||
79 | |||
80 | return { data: videoChannels, total: result.total } | ||
81 | } | ||
82 | } | 82 | } |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index e8ded6ab8..d4b00c07c 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html | |||
@@ -14,7 +14,7 @@ | |||
14 | <div *ngFor="let videos of videoPages" class="videos-page"> | 14 | <div *ngFor="let videos of videoPages" class="videos-page"> |
15 | <my-video-miniature | 15 | <my-video-miniature |
16 | class="ng-animate" | 16 | class="ng-animate" |
17 | *ngFor="let video of videos" [video]="video" [user]="user" | 17 | *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType" |
18 | > | 18 | > |
19 | </my-video-miniature> | 19 | </my-video-miniature> |
20 | </div> | 20 | </div> |
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index 59d3c1ebe..b8fd7f8eb 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts | |||
@@ -11,6 +11,7 @@ import { VideoSortField } from './sort-field.type' | |||
11 | import { Video } from './video.model' | 11 | import { Video } from './video.model' |
12 | import { I18n } from '@ngx-translate/i18n-polyfill' | 12 | import { I18n } from '@ngx-translate/i18n-polyfill' |
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 13 | import { ScreenService } from '@app/shared/misc/screen.service' |
14 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | ||
14 | 15 | ||
15 | export abstract class AbstractVideoList implements OnInit, OnDestroy { | 16 | export abstract class AbstractVideoList implements OnInit, OnDestroy { |
16 | private static LINES_PER_PAGE = 4 | 17 | private static LINES_PER_PAGE = 4 |
@@ -34,6 +35,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
34 | videoWidth: number | 35 | videoWidth: number |
35 | videoHeight: number | 36 | videoHeight: number |
36 | videoPages: Video[][] = [] | 37 | videoPages: Video[][] = [] |
38 | ownerDisplayType: OwnerDisplayType = 'account' | ||
37 | 39 | ||
38 | protected baseVideoWidth = 215 | 40 | protected baseVideoWidth = 215 |
39 | protected baseVideoHeight = 230 | 41 | protected baseVideoHeight = 230 |
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts index d346f985c..fa4ca7f93 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts | |||
@@ -1,14 +1,8 @@ | |||
1 | import { | 1 | import { UserRight, VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared' |
2 | UserRight, | ||
3 | VideoChannel, | ||
4 | VideoConstant, | ||
5 | VideoDetails as VideoDetailsServerModel, | ||
6 | VideoFile, | ||
7 | VideoState | ||
8 | } from '../../../../../shared' | ||
9 | import { AuthUser } from '../../core' | 2 | import { AuthUser } from '../../core' |
10 | import { Video } from '../../shared/video/video.model' | 3 | import { Video } from '../../shared/video/video.model' |
11 | import { Account } from '@app/shared/account/account.model' | 4 | import { Account } from '@app/shared/account/account.model' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
12 | 6 | ||
13 | export class VideoDetails extends Video implements VideoDetailsServerModel { | 7 | export class VideoDetails extends Video implements VideoDetailsServerModel { |
14 | descriptionPath: string | 8 | descriptionPath: string |
@@ -30,7 +24,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
30 | 24 | ||
31 | this.descriptionPath = hash.descriptionPath | 25 | this.descriptionPath = hash.descriptionPath |
32 | this.files = hash.files | 26 | this.files = hash.files |
33 | this.channel = hash.channel | 27 | this.channel = new VideoChannel(hash.channel) |
34 | this.account = new Account(hash.account) | 28 | this.account = new Account(hash.account) |
35 | this.tags = hash.tags | 29 | this.tags = hash.tags |
36 | this.support = hash.support | 30 | this.support = hash.support |
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index 3010e5ccc..de84bccf9 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html | |||
@@ -10,6 +10,12 @@ | |||
10 | </a> | 10 | </a> |
11 | 11 | ||
12 | <span i18n class="video-miniature-created-at-views">{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> | 12 | <span i18n class="video-miniature-created-at-views">{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> |
13 | <a class="video-miniature-account" [routerLink]="[ '/accounts', video.by ]">{{ video.by }}</a> | 13 | |
14 | <a *ngIf="displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]"> | ||
15 | {{ video.byAccount }} | ||
16 | </a> | ||
17 | <a *ngIf="displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]"> | ||
18 | {{ video.byVideoChannel }} | ||
19 | </a> | ||
14 | </div> | 20 | </div> |
15 | </div> | 21 | </div> |
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss index 588eea3a7..6883650f4 100644 --- a/client/src/app/shared/video/video-miniature.component.scss +++ b/client/src/app/shared/video/video-miniature.component.scss | |||
@@ -38,7 +38,8 @@ | |||
38 | font-size: 13px; | 38 | font-size: 13px; |
39 | } | 39 | } |
40 | 40 | ||
41 | .video-miniature-account { | 41 | .video-miniature-account, |
42 | .video-miniature-channel { | ||
42 | @include disable-default-a-behaviour; | 43 | @include disable-default-a-behaviour; |
43 | 44 | ||
44 | display: block; | 45 | display: block; |
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index d3f6dc1f6..07193ebd5 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts | |||
@@ -1,20 +1,51 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { User } from '../users' | 2 | import { User } from '../users' |
3 | import { Video } from './video.model' | 3 | import { Video } from './video.model' |
4 | import { ServerService } from '@app/core' | 4 | import { ServerService } from '@app/core' |
5 | 5 | ||
6 | export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' | ||
7 | |||
6 | @Component({ | 8 | @Component({ |
7 | selector: 'my-video-miniature', | 9 | selector: 'my-video-miniature', |
8 | styleUrls: [ './video-miniature.component.scss' ], | 10 | styleUrls: [ './video-miniature.component.scss' ], |
9 | templateUrl: './video-miniature.component.html' | 11 | templateUrl: './video-miniature.component.html' |
10 | }) | 12 | }) |
11 | export class VideoMiniatureComponent { | 13 | export class VideoMiniatureComponent implements OnInit { |
12 | @Input() user: User | 14 | @Input() user: User |
13 | @Input() video: Video | 15 | @Input() video: Video |
16 | @Input() ownerDisplayType: OwnerDisplayType = 'account' | ||
17 | |||
18 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' | ||
14 | 19 | ||
15 | constructor (private serverService: ServerService) { } | 20 | constructor (private serverService: ServerService) { } |
16 | 21 | ||
22 | ngOnInit () { | ||
23 | if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') { | ||
24 | this.ownerDisplayTypeChosen = this.ownerDisplayType | ||
25 | return | ||
26 | } | ||
27 | |||
28 | // If the video channel name an UUID (not really displayable, we changed this behaviour in v1.0.0-beta.12) | ||
29 | // -> Use the account name | ||
30 | if ( | ||
31 | this.video.channel.name === `${this.video.account.name}_channel` || | ||
32 | this.video.channel.name.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | ||
33 | ) { | ||
34 | this.ownerDisplayTypeChosen = 'account' | ||
35 | } else { | ||
36 | this.ownerDisplayTypeChosen = 'videoChannel' | ||
37 | } | ||
38 | } | ||
39 | |||
17 | isVideoBlur () { | 40 | isVideoBlur () { |
18 | return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) | 41 | return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) |
19 | } | 42 | } |
43 | |||
44 | displayOwnerAccount () { | ||
45 | return this.ownerDisplayTypeChosen === 'account' | ||
46 | } | ||
47 | |||
48 | displayOwnerVideoChannel () { | ||
49 | return this.ownerDisplayTypeChosen === 'videoChannel' | ||
50 | } | ||
20 | } | 51 | } |
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index df8253301..d80c10459 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts | |||
@@ -8,9 +8,12 @@ import { Actor } from '@app/shared/actor/actor.model' | |||
8 | import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' | 8 | import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' |
9 | 9 | ||
10 | export class Video implements VideoServerModel { | 10 | export class Video implements VideoServerModel { |
11 | by: string | 11 | byVideoChannel: string |
12 | byAccount: string | ||
13 | |||
12 | accountAvatarUrl: string | 14 | accountAvatarUrl: string |
13 | videoChannelAvatarUrl: string | 15 | videoChannelAvatarUrl: string |
16 | |||
14 | createdAt: Date | 17 | createdAt: Date |
15 | updatedAt: Date | 18 | updatedAt: Date |
16 | publishedAt: Date | 19 | publishedAt: Date |
@@ -110,7 +113,8 @@ export class Video implements VideoServerModel { | |||
110 | this.account = hash.account | 113 | this.account = hash.account |
111 | this.channel = hash.channel | 114 | this.channel = hash.channel |
112 | 115 | ||
113 | this.by = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) | 116 | this.byAccount = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) |
117 | this.byVideoChannel = Actor.CREATE_BY_STRING(hash.channel.name, hash.channel.host) | ||
114 | this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) | 118 | this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) |
115 | this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel) | 119 | this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel) |
116 | 120 | ||
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index e44f1ee65..1a934c8e2 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts | |||
@@ -27,6 +27,7 @@ import { Account } from '@app/shared/account/account.model' | |||
27 | import { AccountService } from '@app/shared/account/account.service' | 27 | import { AccountService } from '@app/shared/account/account.service' |
28 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 28 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
29 | import { ServerService } from '@app/core' | 29 | import { ServerService } from '@app/core' |
30 | import { UserSubscriptionService } from '@app/shared/user-subscription' | ||
30 | 31 | ||
31 | @Injectable() | 32 | @Injectable() |
32 | export class VideoService { | 33 | export class VideoService { |
@@ -157,6 +158,23 @@ export class VideoService { | |||
157 | ) | 158 | ) |
158 | } | 159 | } |
159 | 160 | ||
161 | getUserSubscriptionVideos ( | ||
162 | videoPagination: ComponentPagination, | ||
163 | sort: VideoSortField | ||
164 | ): Observable<{ videos: Video[], totalVideos: number }> { | ||
165 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | ||
166 | |||
167 | let params = new HttpParams() | ||
168 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
169 | |||
170 | return this.authHttp | ||
171 | .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params }) | ||
172 | .pipe( | ||
173 | switchMap(res => this.extractVideos(res)), | ||
174 | catchError(err => this.restExtractor.handleError(err)) | ||
175 | ) | ||
176 | } | ||
177 | |||
160 | getVideos ( | 178 | getVideos ( |
161 | videoPagination: ComponentPagination, | 179 | videoPagination: ComponentPagination, |
162 | sort: VideoSortField, | 180 | sort: VideoSortField, |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index c275258ef..8a49e3566 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -42,16 +42,17 @@ | |||
42 | 42 | ||
43 | <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> | 43 | <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> |
44 | </a> | 44 | </a> |
45 | <!-- Here will be the subscribe button --> | 45 | |
46 | <my-subscribe-button [videoChannel]="video.channel"></my-subscribe-button> | ||
46 | </div> | 47 | </div> |
47 | 48 | ||
48 | <div class="video-info-by"> | 49 | <div class="video-info-by"> |
49 | <a [routerLink]="[ '/accounts', video.by ]" i18n-title title="Go to the account page"> | 50 | <a [routerLink]="[ '/accounts', video.byAccount ]" i18n-title title="Go to the account page"> |
50 | <span i18n>By {{ video.by }}</span> | 51 | <span i18n>By {{ video.byAccount }}</span> |
51 | <img [src]="video.accountAvatarUrl" alt="Account avatar" /> | 52 | <img [src]="video.accountAvatarUrl" alt="Account avatar" /> |
52 | </a> | 53 | </a> |
53 | 54 | ||
54 | <my-help helpType="custom" i18n-customHtml customHtml="You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@{{video.account.name}}@{{video.account.host}}</strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>."></my-help> | 55 | <my-help helpType="custom" i18n-customHtml customHtml="You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@{{video.account.name}}@{{video.account.host}}</strong> and subscribe there."></my-help> |
55 | </div> | 56 | </div> |
56 | </div> | 57 | </div> |
57 | 58 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index 1354de32e..5bf2f485a 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -125,6 +125,14 @@ | |||
125 | margin: -2px 2px 0 5px; | 125 | margin: -2px 2px 0 5px; |
126 | } | 126 | } |
127 | } | 127 | } |
128 | |||
129 | my-subscribe-button { | ||
130 | /deep/ span[role=button] { | ||
131 | font-size: 13px !important; | ||
132 | } | ||
133 | |||
134 | margin-left: 5px; | ||
135 | } | ||
128 | } | 136 | } |
129 | 137 | ||
130 | .video-info-by { | 138 | .video-info-by { |
@@ -369,7 +377,10 @@ | |||
369 | 377 | ||
370 | .video-miniature-information { | 378 | .video-miniature-information { |
371 | flex-grow: 1; | 379 | flex-grow: 1; |
372 | margin-left: 10px; | 380 | } |
381 | |||
382 | .video-thumbnail { | ||
383 | margin-right: 10px | ||
373 | } | 384 | } |
374 | } | 385 | } |
375 | } | 386 | } |
@@ -502,10 +513,6 @@ | |||
502 | .other-videos { | 513 | .other-videos { |
503 | /deep/ .video-miniature { | 514 | /deep/ .video-miniature { |
504 | flex-direction: column; | 515 | flex-direction: column; |
505 | |||
506 | .video-miniature-information { | ||
507 | margin-left: 0 !important; | ||
508 | } | ||
509 | } | 516 | } |
510 | } | 517 | } |
511 | 518 | ||
diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts new file mode 100644 index 000000000..6e8959c54 --- /dev/null +++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { immutableAssign } from '@app/shared/misc/utils' | ||
4 | import { Location } from '@angular/common' | ||
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | ||
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | ||
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | ||
9 | import { VideoService } from '../../shared/video/video.service' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
11 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
12 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | ||
13 | |||
14 | @Component({ | ||
15 | selector: 'my-videos-user-subscriptions', | ||
16 | styleUrls: [ '../../shared/video/abstract-video-list.scss' ], | ||
17 | templateUrl: '../../shared/video/abstract-video-list.html' | ||
18 | }) | ||
19 | export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
20 | titlePage: string | ||
21 | currentRoute = '/videos/subscriptions' | ||
22 | sort = '-publishedAt' as VideoSortField | ||
23 | ownerDisplayType: OwnerDisplayType = 'auto' | ||
24 | |||
25 | constructor ( | ||
26 | protected router: Router, | ||
27 | protected route: ActivatedRoute, | ||
28 | protected notificationsService: NotificationsService, | ||
29 | protected authService: AuthService, | ||
30 | protected location: Location, | ||
31 | protected i18n: I18n, | ||
32 | protected screenService: ScreenService, | ||
33 | private videoService: VideoService | ||
34 | ) { | ||
35 | super() | ||
36 | |||
37 | this.titlePage = i18n('Videos from your subscriptions') | ||
38 | } | ||
39 | |||
40 | ngOnInit () { | ||
41 | super.ngOnInit() | ||
42 | } | ||
43 | |||
44 | ngOnDestroy () { | ||
45 | super.ngOnDestroy() | ||
46 | } | ||
47 | |||
48 | getVideosObservable (page: number) { | ||
49 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | ||
50 | |||
51 | return this.videoService.getUserSubscriptionVideos(newPagination, this.sort) | ||
52 | } | ||
53 | |||
54 | generateSyndicationList () { | ||
55 | // not implemented yet | ||
56 | } | ||
57 | } | ||
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts index 538a43c6d..18ed52570 100644 --- a/client/src/app/videos/videos-routing.module.ts +++ b/client/src/app/videos/videos-routing.module.ts | |||
@@ -5,6 +5,7 @@ import { MetaGuard } from '@ngx-meta/core' | |||
5 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | 5 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
6 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 6 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' | ||
8 | 9 | ||
9 | const videosRoutes: Routes = [ | 10 | const videosRoutes: Routes = [ |
10 | { | 11 | { |
@@ -13,11 +14,6 @@ const videosRoutes: Routes = [ | |||
13 | canActivateChild: [ MetaGuard ], | 14 | canActivateChild: [ MetaGuard ], |
14 | children: [ | 15 | children: [ |
15 | { | 16 | { |
16 | path: 'list', | ||
17 | pathMatch: 'full', | ||
18 | redirectTo: 'recently-added' | ||
19 | }, | ||
20 | { | ||
21 | path: 'trending', | 17 | path: 'trending', |
22 | component: VideoTrendingComponent, | 18 | component: VideoTrendingComponent, |
23 | data: { | 19 | data: { |
@@ -36,6 +32,15 @@ const videosRoutes: Routes = [ | |||
36 | } | 32 | } |
37 | }, | 33 | }, |
38 | { | 34 | { |
35 | path: 'subscriptions', | ||
36 | component: VideoUserSubscriptionsComponent, | ||
37 | data: { | ||
38 | meta: { | ||
39 | title: 'Subscriptions' | ||
40 | } | ||
41 | } | ||
42 | }, | ||
43 | { | ||
39 | path: 'local', | 44 | path: 'local', |
40 | component: VideoLocalComponent, | 45 | component: VideoLocalComponent, |
41 | data: { | 46 | data: { |
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index c38257e08..3c3877273 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts | |||
@@ -5,6 +5,7 @@ import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.c | |||
5 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 5 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
6 | import { VideosRoutingModule } from './videos-routing.module' | 6 | import { VideosRoutingModule } from './videos-routing.module' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' | ||
8 | 9 | ||
9 | @NgModule({ | 10 | @NgModule({ |
10 | imports: [ | 11 | imports: [ |
@@ -17,7 +18,8 @@ import { VideosComponent } from './videos.component' | |||
17 | 18 | ||
18 | VideoTrendingComponent, | 19 | VideoTrendingComponent, |
19 | VideoRecentlyAddedComponent, | 20 | VideoRecentlyAddedComponent, |
20 | VideoLocalComponent | 21 | VideoLocalComponent, |
22 | VideoUserSubscriptionsComponent | ||
21 | ], | 23 | ], |
22 | 24 | ||
23 | exports: [ | 25 | exports: [ |
diff --git a/client/src/assets/images/menu/podcasts.svg b/client/src/assets/images/menu/podcasts.svg new file mode 100644 index 000000000..cd6efc54e --- /dev/null +++ b/client/src/assets/images/menu/podcasts.svg | |||
@@ -0,0 +1,26 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||
3 | <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch --> | ||
4 | <title>podcasts</title> | ||
5 | <desc>Created with Sketch.</desc> | ||
6 | <defs> | ||
7 | <linearGradient x1="50%" y1="0%" x2="50%" y2="97.3333865%" id="linearGradient-1"> | ||
8 | <stop stop-color="#808080" offset="0%"></stop> | ||
9 | <stop stop-color="#808080" stop-opacity="0.247310915" offset="100%"></stop> | ||
10 | </linearGradient> | ||
11 | <linearGradient x1="50%" y1="0%" x2="50%" y2="97.8635204%" id="linearGradient-2"> | ||
12 | <stop stop-color="#808080" offset="0%"></stop> | ||
13 | <stop stop-color="#808080" stop-opacity="0.250707654" offset="100%"></stop> | ||
14 | </linearGradient> | ||
15 | </defs> | ||
16 | <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
17 | <g id="Artboard-4" transform="translate(-532.000000, -775.000000)"> | ||
18 | <g id="312" transform="translate(532.000000, 775.000000)"> | ||
19 | <circle id="Oval-169" fill="#808080" cx="12" cy="10" r="3"></circle> | ||
20 | <path d="M16.3851456,13.8501206 C17.4222377,12.6991612 18,11.4167199 18,10 C18,6.74089158 15.2591084,4 12,4 C8.74089158,4 6,6.74089158 6,10 C6,11.4186069 6.57916224,12.7027674 7.61838071,13.8540306 C7.80341316,14.0590125 8.11958231,14.0751848 8.32456427,13.8901523 C8.52954623,13.7051199 8.5457185,13.3889507 8.36068606,13.1839688 C7.47616718,12.2040844 7,11.148292 7,10 C7,7.29317633 9.29317633,5 12,5 C14.7068237,5 17,7.29317633 17,10 C17,11.1466944 16.5249958,12.2010466 15.6422459,13.1807178 C15.4573954,13.3858639 15.4738483,13.7020185 15.6789944,13.886869 C15.8841405,14.0717195 16.2002951,14.0552666 16.3851456,13.8501206 Z" id="Oval-169" fill="url(#linearGradient-1)" fill-rule="nonzero"></path> | ||
21 | <path d="M17.5678226,18.3077078 C20.3159646,16.4626239 22,13.3733223 22,10 C22,4.4771525 17.5228475,0 12,0 C6.4771525,0 2,4.4771525 2,10 C2,13.3762414 3.68696556,16.4678678 6.43901638,18.3122954 C6.89779529,18.6197696 7.51896613,18.4971129 7.82644029,18.0383339 C8.13391444,17.579555 8.0112577,16.9583842 7.55247879,16.65091 C5.34877306,15.1739839 4,12.7021478 4,10 C4,5.581722 7.581722,2 12,2 C16.418278,2 20,5.581722 20,10 C20,12.699815 18.6535741,15.1697843 16.4529947,16.6472384 C15.9944687,16.9550897 15.8723227,17.5763611 16.180174,18.0348871 C16.4880252,18.4934131 17.1092967,18.6155591 17.5678226,18.3077078 Z" id="Oval-169" fill="url(#linearGradient-2)" fill-rule="nonzero"></path> | ||
22 | <path d="M9.32918137,15.9750882 C9.14737952,14.8842771 9.89826062,14 10.9979131,14 L13.0020869,14 C14.1055038,14 14.8534426,14.8793447 14.6708186,15.9750882 L13.6633817,22.0197096 C13.5731485,22.561109 13.0573397,23 12.5010434,23 L11.4989566,23 C10.9472481,23 10.4276519,22.5659113 10.3366183,22.0197096 L9.32918137,15.9750882 Z" id="Rectangle-217" fill="#808080"></path> | ||
23 | </g> | ||
24 | </g> | ||
25 | </g> | ||
26 | </svg> | ||
diff --git a/client/src/assets/images/menu/subscriptions.svg b/client/src/assets/images/menu/subscriptions.svg new file mode 100644 index 000000000..cd6efc54e --- /dev/null +++ b/client/src/assets/images/menu/subscriptions.svg | |||
@@ -0,0 +1,26 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||
3 | <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch --> | ||
4 | <title>podcasts</title> | ||
5 | <desc>Created with Sketch.</desc> | ||
6 | <defs> | ||
7 | <linearGradient x1="50%" y1="0%" x2="50%" y2="97.3333865%" id="linearGradient-1"> | ||
8 | <stop stop-color="#808080" offset="0%"></stop> | ||
9 | <stop stop-color="#808080" stop-opacity="0.247310915" offset="100%"></stop> | ||
10 | </linearGradient> | ||
11 | <linearGradient x1="50%" y1="0%" x2="50%" y2="97.8635204%" id="linearGradient-2"> | ||
12 | <stop stop-color="#808080" offset="0%"></stop> | ||
13 | <stop stop-color="#808080" stop-opacity="0.250707654" offset="100%"></stop> | ||
14 | </linearGradient> | ||
15 | </defs> | ||
16 | <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
17 | <g id="Artboard-4" transform="translate(-532.000000, -775.000000)"> | ||
18 | <g id="312" transform="translate(532.000000, 775.000000)"> | ||
19 | <circle id="Oval-169" fill="#808080" cx="12" cy="10" r="3"></circle> | ||
20 | <path d="M16.3851456,13.8501206 C17.4222377,12.6991612 18,11.4167199 18,10 C18,6.74089158 15.2591084,4 12,4 C8.74089158,4 6,6.74089158 6,10 C6,11.4186069 6.57916224,12.7027674 7.61838071,13.8540306 C7.80341316,14.0590125 8.11958231,14.0751848 8.32456427,13.8901523 C8.52954623,13.7051199 8.5457185,13.3889507 8.36068606,13.1839688 C7.47616718,12.2040844 7,11.148292 7,10 C7,7.29317633 9.29317633,5 12,5 C14.7068237,5 17,7.29317633 17,10 C17,11.1466944 16.5249958,12.2010466 15.6422459,13.1807178 C15.4573954,13.3858639 15.4738483,13.7020185 15.6789944,13.886869 C15.8841405,14.0717195 16.2002951,14.0552666 16.3851456,13.8501206 Z" id="Oval-169" fill="url(#linearGradient-1)" fill-rule="nonzero"></path> | ||
21 | <path d="M17.5678226,18.3077078 C20.3159646,16.4626239 22,13.3733223 22,10 C22,4.4771525 17.5228475,0 12,0 C6.4771525,0 2,4.4771525 2,10 C2,13.3762414 3.68696556,16.4678678 6.43901638,18.3122954 C6.89779529,18.6197696 7.51896613,18.4971129 7.82644029,18.0383339 C8.13391444,17.579555 8.0112577,16.9583842 7.55247879,16.65091 C5.34877306,15.1739839 4,12.7021478 4,10 C4,5.581722 7.581722,2 12,2 C16.418278,2 20,5.581722 20,10 C20,12.699815 18.6535741,15.1697843 16.4529947,16.6472384 C15.9944687,16.9550897 15.8723227,17.5763611 16.180174,18.0348871 C16.4880252,18.4934131 17.1092967,18.6155591 17.5678226,18.3077078 Z" id="Oval-169" fill="url(#linearGradient-2)" fill-rule="nonzero"></path> | ||
22 | <path d="M9.32918137,15.9750882 C9.14737952,14.8842771 9.89826062,14 10.9979131,14 L13.0020869,14 C14.1055038,14 14.8534426,14.8793447 14.6708186,15.9750882 L13.6633817,22.0197096 C13.5731485,22.561109 13.0573397,23 12.5010434,23 L11.4989566,23 C10.9472481,23 10.4276519,22.5659113 10.3366183,22.0197096 L9.32918137,15.9750882 Z" id="Rectangle-217" fill="#808080"></path> | ||
23 | </g> | ||
24 | </g> | ||
25 | </g> | ||
26 | </svg> | ||
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index dc0ffe912..b2d7c2bec 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -83,6 +83,7 @@ label { | |||
83 | display: flex; | 83 | display: flex; |
84 | align-items: center; | 84 | align-items: center; |
85 | padding-left: $not-expanded-horizontal-margins; | 85 | padding-left: $not-expanded-horizontal-margins; |
86 | padding-right: $not-expanded-horizontal-margins; | ||
86 | } | 87 | } |
87 | 88 | ||
88 | // Override some properties if the main content is expanded (no menu on the left) | 89 | // Override some properties if the main content is expanded (no menu on the left) |
@@ -96,6 +97,7 @@ label { | |||
96 | 97 | ||
97 | .sub-menu { | 98 | .sub-menu { |
98 | padding-left: $expanded-horizontal-margins; | 99 | padding-left: $expanded-horizontal-margins; |
100 | padding-right: $expanded-horizontal-margins; | ||
99 | } | 101 | } |
100 | } | 102 | } |
101 | } | 103 | } |
@@ -294,6 +296,10 @@ table { | |||
294 | 296 | ||
295 | .sub-menu { | 297 | .sub-menu { |
296 | padding-left: 50px; | 298 | padding-left: 50px; |
299 | |||
300 | .title-page { | ||
301 | font-size: 15px; | ||
302 | } | ||
297 | } | 303 | } |
298 | } | 304 | } |
299 | } | 305 | } |
@@ -316,6 +322,7 @@ table { | |||
316 | 322 | ||
317 | .sub-menu { | 323 | .sub-menu { |
318 | padding-left: 15px; | 324 | padding-left: 15px; |
325 | padding-right: 15px; | ||
319 | margin-bottom: 10px; | 326 | margin-bottom: 10px; |
320 | } | 327 | } |
321 | 328 | ||
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index b0b0f544c..aafe478f9 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -335,6 +335,27 @@ | |||
335 | font-size: 13px; | 335 | font-size: 13px; |
336 | } | 336 | } |
337 | 337 | ||
338 | @mixin actor-owner { | ||
339 | @include disable-default-a-behaviour; | ||
340 | |||
341 | display: block; | ||
342 | font-size: 13px; | ||
343 | margin-top: 4px; | ||
344 | color: #000; | ||
345 | |||
346 | span:hover { | ||
347 | opacity: 0.8; | ||
348 | } | ||
349 | |||
350 | img { | ||
351 | @include avatar(18px); | ||
352 | |||
353 | margin-left: 7px; | ||
354 | position: relative; | ||
355 | top: -2px; | ||
356 | } | ||
357 | } | ||
358 | |||
338 | @mixin sub-menu-with-actor { | 359 | @mixin sub-menu-with-actor { |
339 | height: 160px; | 360 | height: 160px; |
340 | display: flex; | 361 | display: flex; |
@@ -371,7 +392,7 @@ | |||
371 | position: relative; | 392 | position: relative; |
372 | top: 3px; | 393 | top: 3px; |
373 | font-size: 14px; | 394 | font-size: 14px; |
374 | color: #777272; | 395 | color: $grey-actor-name; |
375 | } | 396 | } |
376 | } | 397 | } |
377 | 398 | ||
@@ -380,24 +401,7 @@ | |||
380 | } | 401 | } |
381 | 402 | ||
382 | .actor-owner { | 403 | .actor-owner { |
383 | @include disable-default-a-behaviour; | 404 | @include actor-owner; |
384 | |||
385 | display: block; | ||
386 | font-size: 13px; | ||
387 | margin-top: 4px; | ||
388 | color: #000; | ||
389 | |||
390 | span:hover { | ||
391 | opacity: 0.8; | ||
392 | } | ||
393 | |||
394 | img { | ||
395 | @include avatar(18px); | ||
396 | |||
397 | margin-left: 7px; | ||
398 | position: relative; | ||
399 | top: -2px; | ||
400 | } | ||
401 | } | 405 | } |
402 | } | 406 | } |
403 | } | 407 | } |
@@ -426,3 +430,18 @@ | |||
426 | background-image: url($imageUrl); | 430 | background-image: url($imageUrl); |
427 | } | 431 | } |
428 | } | 432 | } |
433 | |||
434 | @mixin row-blocks { | ||
435 | display: flex; | ||
436 | min-height: 130px; | ||
437 | padding-bottom: 20px; | ||
438 | margin-bottom: 20px; | ||
439 | border-bottom: 1px solid #C6C6C6; | ||
440 | |||
441 | @media screen and (max-width: 800px) { | ||
442 | flex-direction: column; | ||
443 | height: auto; | ||
444 | text-align: center; | ||
445 | align-items: center; | ||
446 | } | ||
447 | } \ No newline at end of file | ||
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index f1f755126..e6db98642 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss | |||
@@ -12,6 +12,8 @@ $black-background: #000; | |||
12 | $grey-background: #f6f2f2; | 12 | $grey-background: #f6f2f2; |
13 | $red-error: #FF0000; | 13 | $red-error: #FF0000; |
14 | 14 | ||
15 | $grey-actor-name: #777272; | ||
16 | |||
15 | $expanded-horizontal-margins: 150px; | 17 | $expanded-horizontal-margins: 150px; |
16 | $not-expanded-horizontal-margins: 30px; | 18 | $not-expanded-horizontal-margins: 30px; |
17 | 19 | ||
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index 282dde268..36d0f237b 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { getServerActor } from '../../../helpers/utils' | 3 | import { CONFIG, REMOTE_SCHEME, sequelizeTypescript } from '../../../initializers' |
4 | import { REMOTE_SCHEME, sequelizeTypescript } from '../../../initializers' | ||
5 | import { sendFollow } from '../../activitypub/send' | 4 | import { sendFollow } from '../../activitypub/send' |
6 | import { sanitizeHost } from '../../../helpers/core-utils' | 5 | import { sanitizeHost } from '../../../helpers/core-utils' |
7 | import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' | 6 | import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' |
@@ -22,10 +21,14 @@ async function processActivityPubFollow (job: Bull.Job) { | |||
22 | 21 | ||
23 | logger.info('Processing ActivityPub follow in job %d.', job.id) | 22 | logger.info('Processing ActivityPub follow in job %d.', job.id) |
24 | 23 | ||
25 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) | 24 | let targetActor: ActorModel |
26 | 25 | if (!host || host === CONFIG.WEBSERVER.HOST) { | |
27 | const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) | 26 | targetActor = await ActorModel.loadLocalByName(payload.name) |
28 | const targetActor = await getOrCreateActorAndServerAndModel(actorUrl) | 27 | } else { |
28 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) | ||
29 | const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) | ||
30 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl) | ||
31 | } | ||
29 | 32 | ||
30 | const fromActor = await ActorModel.load(payload.followerActorId) | 33 | const fromActor = await ActorModel.load(payload.followerActorId) |
31 | 34 | ||
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 20d3aa5fc..b2d7ace66 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -29,6 +29,7 @@ import { getSort } from '../utils' | |||
29 | import { ActorModel } from './actor' | 29 | import { ActorModel } from './actor' |
30 | import { VideoChannelModel } from '../video/video-channel' | 30 | import { VideoChannelModel } from '../video/video-channel' |
31 | import { IIncludeOptions } from '../../../node_modules/sequelize-typescript/lib/interfaces/IIncludeOptions' | 31 | import { IIncludeOptions } from '../../../node_modules/sequelize-typescript/lib/interfaces/IIncludeOptions' |
32 | import { AccountModel } from '../account/account' | ||
32 | 33 | ||
33 | @Table({ | 34 | @Table({ |
34 | tableName: 'actorFollow', | 35 | tableName: 'actorFollow', |
@@ -262,7 +263,13 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
262 | include: [ | 263 | include: [ |
263 | { | 264 | { |
264 | model: VideoChannelModel, | 265 | model: VideoChannelModel, |
265 | required: true | 266 | required: true, |
267 | include: [ | ||
268 | { | ||
269 | model: AccountModel, | ||
270 | required: true | ||
271 | } | ||
272 | ] | ||
266 | } | 273 | } |
267 | ] | 274 | ] |
268 | } | 275 | } |