Add local user subscriptions

This commit is contained in:
Chocobozzz 2018-08-21 16:18:59 +02:00
parent 99492dbc0d
commit 22a16e36f6
42 changed files with 647 additions and 98 deletions

View File

@ -2,7 +2,7 @@
@import '_mixins'; @import '_mixins';
.row { .row {
text-align: center; justify-content: center;
} }
a.video-channel { a.video-channel {

View File

@ -9,6 +9,7 @@ import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-vid
import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
const myAccountRoutes: Routes = [ const myAccountRoutes: Routes = [
{ {
@ -74,6 +75,15 @@ const myAccountRoutes: Routes = [
title: 'Account video imports' title: 'Account video imports'
} }
} }
},
{
path: 'subscriptions',
component: MyAccountSubscriptionsComponent,
data: {
meta: {
title: 'Account subscriptions'
}
}
} }
] ]
} }

View File

@ -0,0 +1,23 @@
<div class="video-channels">
<div *ngFor="let videoChannel of videoChannels" class="video-channel">
<a [routerLink]="[ '/video-channels', videoChannel.name ]">
<img [src]="videoChannel.avatarUrl" alt="Avatar" />
</a>
<div class="video-channel-info">
<a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names" i18n-title title="Go to the channel">
<div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
<div class="video-channel-name">{{ videoChannel.name }}</div>
</a>
<div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
<a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Go the owner account page" class="actor-owner">
<span i18n>Created by {{ videoChannel.ownerBy }}</span>
<img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" />
</a>
</div>
<my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button>
</div>
</div>

View File

@ -0,0 +1,49 @@
@import '_variables';
@import '_mixins';
.video-channel {
@include row-blocks;
img {
@include avatar(80px);
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: #000;
.video-channel-display-name {
font-weight: $font-semibold;
font-size: 18px;
}
.video-channel-name {
font-size: 14px;
color: $grey-actor-name;
margin-left: 5px;
}
}
}
.actor-owner {
@include actor-owner;
}
my-subscribe-button {
/deep/ span[role=button] {
padding: 7px 12px;
font-size: 16px;
}
}
}

View File

@ -0,0 +1,30 @@
import { Component, OnInit } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { UserSubscriptionService } from '@app/shared/user-subscription'
@Component({
selector: 'my-account-subscriptions',
templateUrl: './my-account-subscriptions.component.html',
styleUrls: [ './my-account-subscriptions.component.scss' ]
})
export class MyAccountSubscriptionsComponent implements OnInit {
videoChannels: VideoChannel[] = []
constructor (
private userSubscriptionService: UserSubscriptionService,
private notificationsService: NotificationsService,
private i18n: I18n
) {}
ngOnInit () {
this.userSubscriptionService.listSubscriptions()
.subscribe(
res => { console.log(res); this.videoChannels = res.data },
error => this.notificationsService.error(this.i18n('Error'), error.message)
)
}
}

View File

@ -78,7 +78,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
support: body.support || null support: body.support || null
} }
this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe( this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe(
() => { () => {
this.authService.refreshUserInformation() this.authService.refreshUserInformation()
this.notificationsService.success( this.notificationsService.success(
@ -93,7 +93,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
} }
onAvatarChange (formData: FormData) { onAvatarChange (formData: FormData) {
this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.uuid, formData) this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData)
.subscribe( .subscribe(
data => { data => {
this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.'))

View File

@ -12,11 +12,7 @@
} }
.video-channel { .video-channel {
display: flex; @include row-blocks;
min-height: 130px;
padding-bottom: 20px;
margin-bottom: 20px;
border-bottom: 1px solid #C6C6C6;
img { img {
@include avatar(80px); @include avatar(80px);
@ -42,7 +38,7 @@
.video-channel-name { .video-channel-name {
font-size: 14px; font-size: 14px;
color: #777272; color: $grey-actor-name;
margin-left: 5px; margin-left: 5px;
} }
} }
@ -64,12 +60,9 @@
} }
.video-channel { .video-channel {
flex-direction: column;
height: auto;
text-align: center;
.video-channel-names { .video-channel-names {
justify-content: center; flex-direction: column;
align-items: center !important;
} }
img { img {

View File

@ -42,11 +42,7 @@
} }
.video { .video {
display: flex; @include row-blocks;
min-height: 130px;
padding-bottom: 20px;
margin-bottom: 20px;
border-bottom: 1px solid #C6C6C6;
&:first-child { &:first-child {
margin-top: 47px; margin-top: 47px;

View File

@ -2,11 +2,13 @@
<div class="sub-menu"> <div class="sub-menu">
<a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a> <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
<a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a> <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My channels</a>
<a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a> <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
<a *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My video imports</a> <a i18n routerLink="/my-account/subscriptions" routerLinkActive="active" class="title-page">My subscriptions</a>
<a *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My imports</a>
</div> </div>
<div class="margin-content"> <div class="margin-content">

View File

@ -14,6 +14,7 @@ import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-accoun
import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
@NgModule({ @NgModule({
imports: [ imports: [
@ -34,7 +35,8 @@ import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settin
MyAccountVideoChannelUpdateComponent, MyAccountVideoChannelUpdateComponent,
ActorAvatarInfoComponent, ActorAvatarInfoComponent,
MyAccountVideoImportsComponent, MyAccountVideoImportsComponent,
MyAccountDangerZoneComponent MyAccountDangerZoneComponent,
MyAccountSubscriptionsComponent
], ],
exports: [ exports: [

View File

@ -25,7 +25,7 @@
position: relative; position: relative;
top: 2px; top: 2px;
font-size: 14px; font-size: 14px;
color: #777272; color: $grey-actor-name;
} }
} }

View File

@ -8,6 +8,8 @@
<div class="actor-names"> <div class="actor-names">
<div class="actor-display-name">{{ videoChannel.displayName }}</div> <div class="actor-display-name">{{ videoChannel.displayName }}</div>
<div class="actor-name">{{ videoChannel.nameWithHost }}</div> <div class="actor-name">{{ videoChannel.nameWithHost }}</div>
<my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button>
</div> </div>
<div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div> <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div>
@ -20,7 +22,6 @@
<div class="links"> <div class="links">
<a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
<a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
</div> </div>
</div> </div>

View File

@ -3,4 +3,19 @@
.sub-menu { .sub-menu {
@include sub-menu-with-actor; @include sub-menu-with-actor;
.actor, .actor-info {
width: 100%;
}
.actor-name {
flex-grow: 1;
}
my-subscribe-button {
/deep/ span[role=button] {
padding: 7px 12px;
font-size: 16px;
}
}
} }

View File

@ -42,6 +42,11 @@
<div class="panel-block"> <div class="panel-block">
<div i18n class="block-title">Videos</div> <div i18n class="block-title">Videos</div>
<a *ngIf="isLoggedIn" routerLink="/videos/subscriptions" routerLinkActive="active">
<span class="icon icon-videos-subscriptions"></span>
<ng-container i18n>Subscriptions</ng-container>
</a>
<a routerLink="/videos/trending" routerLinkActive="active"> <a routerLink="/videos/trending" routerLinkActive="active">
<span class="icon icon-videos-trending"></span> <span class="icon icon-videos-trending"></span>
<ng-container i18n>Trending</ng-container> <ng-container i18n>Trending</ng-container>

View File

@ -135,6 +135,12 @@ menu {
margin-right: 18px; margin-right: 18px;
&.icon-videos-subscriptions {
position: relative;
top: -2px;
background-image: url('../../assets/images/menu/subscriptions.svg');
}
&.icon-videos-trending { &.icon-videos-trending {
position: relative; position: relative;
top: -2px; top: -2px;

View File

@ -36,7 +36,8 @@ import {
ReactiveFileComponent, ReactiveFileComponent,
ResetPasswordValidatorsService, ResetPasswordValidatorsService,
UserValidatorsService, UserValidatorsService,
VideoAbuseValidatorsService, VideoBlacklistValidatorsService, VideoAbuseValidatorsService,
VideoBlacklistValidatorsService,
VideoChannelValidatorsService, VideoChannelValidatorsService,
VideoCommentValidatorsService, VideoCommentValidatorsService,
VideoValidatorsService VideoValidatorsService
@ -49,6 +50,7 @@ import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.c
import { VideoImportService } from '@app/shared/video-import/video-import.service' import { VideoImportService } from '@app/shared/video-import/video-import.service'
import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
@NgModule({ @NgModule({
imports: [ imports: [
@ -83,7 +85,8 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
InfiniteScrollerDirective, InfiniteScrollerDirective,
HelpComponent, HelpComponent,
ReactiveFileComponent, ReactiveFileComponent,
PeertubeCheckboxComponent PeertubeCheckboxComponent,
SubscribeButtonComponent
], ],
exports: [ exports: [
@ -115,6 +118,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
HelpComponent, HelpComponent,
ReactiveFileComponent, ReactiveFileComponent,
PeertubeCheckboxComponent, PeertubeCheckboxComponent,
SubscribeButtonComponent,
NumberFormatterPipe, NumberFormatterPipe,
ObjectLengthPipe, ObjectLengthPipe,
@ -134,6 +138,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
VideoChannelService, VideoChannelService,
VideoCaptionService, VideoCaptionService,
VideoImportService, VideoImportService,
UserSubscriptionService,
FormValidatorService, FormValidatorService,
CustomConfigValidatorsService, CustomConfigValidatorsService,

View File

@ -0,0 +1,2 @@
export * from './user-subscription.service'
export * from './subscribe-button.component'

View File

@ -0,0 +1,15 @@
<span i18n *ngIf="subscribed === false" class="subscribe-button" role="button" (click)="subscribe()">
<span>Subscribe</span>
<span *ngIf="displayFollowers && videoChannel.followersCount !== 0" class="followers-count">
{{ videoChannel.followersCount | myNumberFormatter }}
</span>
</span>
<span *ngIf="subscribed === true" class="unsubscribe-button" role="button" (click)="unsubscribe()">
<span class="subscribed" i18n>Subscribed</span>
<span class="unsubscribe" i18n>Unsubscribe</span>
<span *ngIf="displayFollowers && videoChannel.followersCount !== 0" class="followers-count">
{{ videoChannel.followersCount | myNumberFormatter }}
</span>
</span>

View File

@ -0,0 +1,37 @@
@import '_variables';
@import '_mixins';
.subscribe-button {
@include peertube-button;
@include orange-button;
}
.unsubscribe-button {
@include peertube-button;
@include grey-button
}
.subscribe-button,
.unsubscribe-button {
padding: 3px 7px;
}
.unsubscribe-button {
.subscribed {
display: inline;
}
.unsubscribe {
display: none;
}
&:hover {
.subscribed {
display: none;
}
.unsubscribe {
display: inline;
}
}
}

View File

@ -0,0 +1,74 @@
import { Component, Input, OnInit } from '@angular/core'
import { AuthService } from '@app/core'
import { RestExtractor } from '@app/shared/rest'
import { RedirectService } from '@app/core/routing/redirect.service'
import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { NotificationsService } from 'angular2-notifications'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Component({
selector: 'my-subscribe-button',
templateUrl: './subscribe-button.component.html',
styleUrls: [ './subscribe-button.component.scss' ]
})
export class SubscribeButtonComponent implements OnInit {
@Input() videoChannel: VideoChannel
@Input() displayFollowers = false
subscribed: boolean
constructor (
private authService: AuthService,
private restExtractor: RestExtractor,
private redirectService: RedirectService,
private notificationsService: NotificationsService,
private userSubscriptionService: UserSubscriptionService,
private i18n: I18n
) { }
get uri () {
return this.videoChannel.name + '@' + this.videoChannel.host
}
ngOnInit () {
this.userSubscriptionService.isSubscriptionExists(this.uri)
.subscribe(
exists => this.subscribed = exists,
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
subscribe () {
this.userSubscriptionService.addSubscription(this.uri)
.subscribe(
() => {
this.subscribed = true
this.notificationsService.success(
this.i18n('Subscribed'),
this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName })
)
},
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
unsubscribe () {
this.userSubscriptionService.deleteSubscription(this.uri)
.subscribe(
() => {
this.subscribed = false
this.notificationsService.success(
this.i18n('Unsubscribed'),
this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName })
)
},
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
}

View File

@ -0,0 +1,66 @@
import { catchError, map } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { ResultList } from '../../../../../shared'
import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest'
import { Observable, of } from 'rxjs'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
import { VideoChannel as VideoChannelServer } from '../../../../../shared/models/videos'
@Injectable()
export class UserSubscriptionService {
static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/users/me/subscriptions'
constructor (
private authHttp: HttpClient,
private restExtractor: RestExtractor
) {
}
deleteSubscription (nameWithHost: string) {
const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost
return this.authHttp.delete(url)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err))
)
}
addSubscription (nameWithHost: string) {
const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
const body = { uri: nameWithHost }
return this.authHttp.post(url, body)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err))
)
}
listSubscriptions (): Observable<ResultList<VideoChannel>> {
const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
return this.authHttp.get<ResultList<VideoChannelServer>>(url)
.pipe(
map(res => VideoChannelService.extractVideoChannels(res)),
catchError(err => this.restExtractor.handleError(err))
)
}
isSubscriptionExists (nameWithHost: string): Observable<boolean> {
const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost
return this.authHttp.get(url)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => {
if (err.status === 404) return of(false)
return this.restExtractor.handleError(err)
})
)
}
}

View File

@ -22,6 +22,16 @@ export class VideoChannelService {
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
static extractVideoChannels (result: ResultList<VideoChannelServer>) {
const videoChannels: VideoChannel[] = []
for (const videoChannelJSON of result.data) {
videoChannels.push(new VideoChannel(videoChannelJSON))
}
return { data: videoChannels, total: result.total }
}
getVideoChannel (videoChannelName: string) { getVideoChannel (videoChannelName: string) {
return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName) return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName)
.pipe( .pipe(
@ -34,7 +44,7 @@ export class VideoChannelService {
listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> { listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> {
return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels')
.pipe( .pipe(
map(res => this.extractVideoChannels(res)), map(res => VideoChannelService.extractVideoChannels(res)),
catchError(err => this.restExtractor.handleError(err)) catchError(err => this.restExtractor.handleError(err))
) )
} }
@ -47,16 +57,16 @@ export class VideoChannelService {
) )
} }
updateVideoChannel (videoChannelUUID: string, videoChannel: VideoChannelUpdate) { updateVideoChannel (videoChannelName: string, videoChannel: VideoChannelUpdate) {
return this.authHttp.put(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID, videoChannel) return this.authHttp.put(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName, videoChannel)
.pipe( .pipe(
map(this.restExtractor.extractDataBool), map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err)) catchError(err => this.restExtractor.handleError(err))
) )
} }
changeVideoChannelAvatar (videoChannelUUID: string, avatarForm: FormData) { changeVideoChannelAvatar (videoChannelName: string, avatarForm: FormData) {
const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID + '/avatar/pick' const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar/pick'
return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm)
.pipe(catchError(err => this.restExtractor.handleError(err))) .pipe(catchError(err => this.restExtractor.handleError(err)))
@ -69,14 +79,4 @@ export class VideoChannelService {
catchError(err => this.restExtractor.handleError(err)) catchError(err => this.restExtractor.handleError(err))
) )
} }
private extractVideoChannels (result: ResultList<VideoChannelServer>) {
const videoChannels: VideoChannel[] = []
for (const videoChannelJSON of result.data) {
videoChannels.push(new VideoChannel(videoChannelJSON))
}
return { data: videoChannels, total: result.total }
}
} }

View File

@ -14,7 +14,7 @@
<div *ngFor="let videos of videoPages" class="videos-page"> <div *ngFor="let videos of videoPages" class="videos-page">
<my-video-miniature <my-video-miniature
class="ng-animate" class="ng-animate"
*ngFor="let video of videos" [video]="video" [user]="user" *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
> >
</my-video-miniature> </my-video-miniature>
</div> </div>

View File

@ -11,6 +11,7 @@ import { VideoSortField } from './sort-field.type'
import { Video } from './video.model' import { Video } from './video.model'
import { I18n } from '@ngx-translate/i18n-polyfill' import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service' import { ScreenService } from '@app/shared/misc/screen.service'
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
export abstract class AbstractVideoList implements OnInit, OnDestroy { export abstract class AbstractVideoList implements OnInit, OnDestroy {
private static LINES_PER_PAGE = 4 private static LINES_PER_PAGE = 4
@ -34,6 +35,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
videoWidth: number videoWidth: number
videoHeight: number videoHeight: number
videoPages: Video[][] = [] videoPages: Video[][] = []
ownerDisplayType: OwnerDisplayType = 'account'
protected baseVideoWidth = 215 protected baseVideoWidth = 215
protected baseVideoHeight = 230 protected baseVideoHeight = 230

View File

@ -1,14 +1,8 @@
import { import { UserRight, VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared'
UserRight,
VideoChannel,
VideoConstant,
VideoDetails as VideoDetailsServerModel,
VideoFile,
VideoState
} from '../../../../../shared'
import { AuthUser } from '../../core' import { AuthUser } from '../../core'
import { Video } from '../../shared/video/video.model' import { Video } from '../../shared/video/video.model'
import { Account } from '@app/shared/account/account.model' import { Account } from '@app/shared/account/account.model'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
export class VideoDetails extends Video implements VideoDetailsServerModel { export class VideoDetails extends Video implements VideoDetailsServerModel {
descriptionPath: string descriptionPath: string
@ -30,7 +24,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
this.descriptionPath = hash.descriptionPath this.descriptionPath = hash.descriptionPath
this.files = hash.files this.files = hash.files
this.channel = hash.channel this.channel = new VideoChannel(hash.channel)
this.account = new Account(hash.account) this.account = new Account(hash.account)
this.tags = hash.tags this.tags = hash.tags
this.support = hash.support this.support = hash.support

View File

@ -10,6 +10,12 @@
</a> </a>
<span i18n class="video-miniature-created-at-views">{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> <span i18n class="video-miniature-created-at-views">{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
<a class="video-miniature-account" [routerLink]="[ '/accounts', video.by ]">{{ video.by }}</a>
<a *ngIf="displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
{{ video.byAccount }}
</a>
<a *ngIf="displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]">
{{ video.byVideoChannel }}
</a>
</div> </div>
</div> </div>

View File

@ -38,7 +38,8 @@
font-size: 13px; font-size: 13px;
} }
.video-miniature-account { .video-miniature-account,
.video-miniature-channel {
@include disable-default-a-behaviour; @include disable-default-a-behaviour;
display: block; display: block;

View File

@ -1,20 +1,51 @@
import { Component, Input } from '@angular/core' import { Component, Input, OnInit } from '@angular/core'
import { User } from '../users' import { User } from '../users'
import { Video } from './video.model' import { Video } from './video.model'
import { ServerService } from '@app/core' import { ServerService } from '@app/core'
export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
@Component({ @Component({
selector: 'my-video-miniature', selector: 'my-video-miniature',
styleUrls: [ './video-miniature.component.scss' ], styleUrls: [ './video-miniature.component.scss' ],
templateUrl: './video-miniature.component.html' templateUrl: './video-miniature.component.html'
}) })
export class VideoMiniatureComponent { export class VideoMiniatureComponent implements OnInit {
@Input() user: User @Input() user: User
@Input() video: Video @Input() video: Video
@Input() ownerDisplayType: OwnerDisplayType = 'account'
private ownerDisplayTypeChosen: 'account' | 'videoChannel'
constructor (private serverService: ServerService) { } constructor (private serverService: ServerService) { }
ngOnInit () {
if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') {
this.ownerDisplayTypeChosen = this.ownerDisplayType
return
}
// If the video channel name an UUID (not really displayable, we changed this behaviour in v1.0.0-beta.12)
// -> Use the account name
if (
this.video.channel.name === `${this.video.account.name}_channel` ||
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}$/)
) {
this.ownerDisplayTypeChosen = 'account'
} else {
this.ownerDisplayTypeChosen = 'videoChannel'
}
}
isVideoBlur () { isVideoBlur () {
return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
} }
displayOwnerAccount () {
return this.ownerDisplayTypeChosen === 'account'
}
displayOwnerVideoChannel () {
return this.ownerDisplayTypeChosen === 'videoChannel'
}
} }

View File

@ -8,9 +8,12 @@ import { Actor } from '@app/shared/actor/actor.model'
import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model'
export class Video implements VideoServerModel { export class Video implements VideoServerModel {
by: string byVideoChannel: string
byAccount: string
accountAvatarUrl: string accountAvatarUrl: string
videoChannelAvatarUrl: string videoChannelAvatarUrl: string
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
publishedAt: Date publishedAt: Date
@ -110,7 +113,8 @@ export class Video implements VideoServerModel {
this.account = hash.account this.account = hash.account
this.channel = hash.channel this.channel = hash.channel
this.by = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) this.byAccount = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host)
this.byVideoChannel = Actor.CREATE_BY_STRING(hash.channel.name, hash.channel.host)
this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account)
this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel) this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel)

View File

@ -27,6 +27,7 @@ import { Account } from '@app/shared/account/account.model'
import { AccountService } from '@app/shared/account/account.service' import { AccountService } from '@app/shared/account/account.service'
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
import { ServerService } from '@app/core' import { ServerService } from '@app/core'
import { UserSubscriptionService } from '@app/shared/user-subscription'
@Injectable() @Injectable()
export class VideoService { export class VideoService {
@ -157,6 +158,23 @@ export class VideoService {
) )
} }
getUserSubscriptionVideos (
videoPagination: ComponentPagination,
sort: VideoSortField
): Observable<{ videos: Video[], totalVideos: number }> {
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
return this.authHttp
.get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params })
.pipe(
switchMap(res => this.extractVideos(res)),
catchError(err => this.restExtractor.handleError(err))
)
}
getVideos ( getVideos (
videoPagination: ComponentPagination, videoPagination: ComponentPagination,
sort: VideoSortField, sort: VideoSortField,

View File

@ -42,16 +42,17 @@
<img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" />
</a> </a>
<!-- Here will be the subscribe button -->
<my-subscribe-button [videoChannel]="video.channel"></my-subscribe-button>
</div> </div>
<div class="video-info-by"> <div class="video-info-by">
<a [routerLink]="[ '/accounts', video.by ]" i18n-title title="Go to the account page"> <a [routerLink]="[ '/accounts', video.byAccount ]" i18n-title title="Go to the account page">
<span i18n>By {{ video.by }}</span> <span i18n>By {{ video.byAccount }}</span>
<img [src]="video.accountAvatarUrl" alt="Account avatar" /> <img [src]="video.accountAvatarUrl" alt="Account avatar" />
</a> </a>
<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> <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>
</div> </div>
</div> </div>

View File

@ -125,6 +125,14 @@
margin: -2px 2px 0 5px; margin: -2px 2px 0 5px;
} }
} }
my-subscribe-button {
/deep/ span[role=button] {
font-size: 13px !important;
}
margin-left: 5px;
}
} }
.video-info-by { .video-info-by {
@ -369,7 +377,10 @@
.video-miniature-information { .video-miniature-information {
flex-grow: 1; flex-grow: 1;
margin-left: 10px; }
.video-thumbnail {
margin-right: 10px
} }
} }
} }
@ -502,10 +513,6 @@
.other-videos { .other-videos {
/deep/ .video-miniature { /deep/ .video-miniature {
flex-direction: column; flex-direction: column;
.video-miniature-information {
margin-left: 0 !important;
}
} }
} }

View File

@ -0,0 +1,57 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { immutableAssign } from '@app/shared/misc/utils'
import { Location } from '@angular/common'
import { NotificationsService } from 'angular2-notifications'
import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoSortField } from '../../shared/video/sort-field.type'
import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service'
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
@Component({
selector: 'my-videos-user-subscriptions',
styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
templateUrl: '../../shared/video/abstract-video-list.html'
})
export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/videos/subscriptions'
sort = '-publishedAt' as VideoSortField
ownerDisplayType: OwnerDisplayType = 'auto'
constructor (
protected router: Router,
protected route: ActivatedRoute,
protected notificationsService: NotificationsService,
protected authService: AuthService,
protected location: Location,
protected i18n: I18n,
protected screenService: ScreenService,
private videoService: VideoService
) {
super()
this.titlePage = i18n('Videos from your subscriptions')
}
ngOnInit () {
super.ngOnInit()
}
ngOnDestroy () {
super.ngOnDestroy()
}
getVideosObservable (page: number) {
const newPagination = immutableAssign(this.pagination, { currentPage: page })
return this.videoService.getUserSubscriptionVideos(newPagination, this.sort)
}
generateSyndicationList () {
// not implemented yet
}
}

View File

@ -5,6 +5,7 @@ import { MetaGuard } from '@ngx-meta/core'
import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
import { VideoTrendingComponent } from './video-list/video-trending.component' import { VideoTrendingComponent } from './video-list/video-trending.component'
import { VideosComponent } from './videos.component' import { VideosComponent } from './videos.component'
import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component'
const videosRoutes: Routes = [ const videosRoutes: Routes = [
{ {
@ -12,11 +13,6 @@ const videosRoutes: Routes = [
component: VideosComponent, component: VideosComponent,
canActivateChild: [ MetaGuard ], canActivateChild: [ MetaGuard ],
children: [ children: [
{
path: 'list',
pathMatch: 'full',
redirectTo: 'recently-added'
},
{ {
path: 'trending', path: 'trending',
component: VideoTrendingComponent, component: VideoTrendingComponent,
@ -35,6 +31,15 @@ const videosRoutes: Routes = [
} }
} }
}, },
{
path: 'subscriptions',
component: VideoUserSubscriptionsComponent,
data: {
meta: {
title: 'Subscriptions'
}
}
},
{ {
path: 'local', path: 'local',
component: VideoLocalComponent, component: VideoLocalComponent,

View File

@ -5,6 +5,7 @@ import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.c
import { VideoTrendingComponent } from './video-list/video-trending.component' import { VideoTrendingComponent } from './video-list/video-trending.component'
import { VideosRoutingModule } from './videos-routing.module' import { VideosRoutingModule } from './videos-routing.module'
import { VideosComponent } from './videos.component' import { VideosComponent } from './videos.component'
import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component'
@NgModule({ @NgModule({
imports: [ imports: [
@ -17,7 +18,8 @@ import { VideosComponent } from './videos.component'
VideoTrendingComponent, VideoTrendingComponent,
VideoRecentlyAddedComponent, VideoRecentlyAddedComponent,
VideoLocalComponent VideoLocalComponent,
VideoUserSubscriptionsComponent
], ],
exports: [ exports: [

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>podcasts</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="97.3333865%" id="linearGradient-1">
<stop stop-color="#808080" offset="0%"></stop>
<stop stop-color="#808080" stop-opacity="0.247310915" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="97.8635204%" id="linearGradient-2">
<stop stop-color="#808080" offset="0%"></stop>
<stop stop-color="#808080" stop-opacity="0.250707654" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard-4" transform="translate(-532.000000, -775.000000)">
<g id="312" transform="translate(532.000000, 775.000000)">
<circle id="Oval-169" fill="#808080" cx="12" cy="10" r="3"></circle>
<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>
<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>
<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>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>podcasts</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="97.3333865%" id="linearGradient-1">
<stop stop-color="#808080" offset="0%"></stop>
<stop stop-color="#808080" stop-opacity="0.247310915" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="97.8635204%" id="linearGradient-2">
<stop stop-color="#808080" offset="0%"></stop>
<stop stop-color="#808080" stop-opacity="0.250707654" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard-4" transform="translate(-532.000000, -775.000000)">
<g id="312" transform="translate(532.000000, 775.000000)">
<circle id="Oval-169" fill="#808080" cx="12" cy="10" r="3"></circle>
<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>
<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>
<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>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -83,6 +83,7 @@ label {
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: $not-expanded-horizontal-margins; padding-left: $not-expanded-horizontal-margins;
padding-right: $not-expanded-horizontal-margins;
} }
// Override some properties if the main content is expanded (no menu on the left) // Override some properties if the main content is expanded (no menu on the left)
@ -96,6 +97,7 @@ label {
.sub-menu { .sub-menu {
padding-left: $expanded-horizontal-margins; padding-left: $expanded-horizontal-margins;
padding-right: $expanded-horizontal-margins;
} }
} }
} }
@ -294,6 +296,10 @@ table {
.sub-menu { .sub-menu {
padding-left: 50px; padding-left: 50px;
.title-page {
font-size: 15px;
}
} }
} }
} }
@ -316,6 +322,7 @@ table {
.sub-menu { .sub-menu {
padding-left: 15px; padding-left: 15px;
padding-right: 15px;
margin-bottom: 10px; margin-bottom: 10px;
} }

View File

@ -335,6 +335,27 @@
font-size: 13px; font-size: 13px;
} }
@mixin actor-owner {
@include disable-default-a-behaviour;
display: block;
font-size: 13px;
margin-top: 4px;
color: #000;
span:hover {
opacity: 0.8;
}
img {
@include avatar(18px);
margin-left: 7px;
position: relative;
top: -2px;
}
}
@mixin sub-menu-with-actor { @mixin sub-menu-with-actor {
height: 160px; height: 160px;
display: flex; display: flex;
@ -371,7 +392,7 @@
position: relative; position: relative;
top: 3px; top: 3px;
font-size: 14px; font-size: 14px;
color: #777272; color: $grey-actor-name;
} }
} }
@ -380,24 +401,7 @@
} }
.actor-owner { .actor-owner {
@include disable-default-a-behaviour; @include actor-owner;
display: block;
font-size: 13px;
margin-top: 4px;
color: #000;
span:hover {
opacity: 0.8;
}
img {
@include avatar(18px);
margin-left: 7px;
position: relative;
top: -2px;
}
} }
} }
} }
@ -426,3 +430,18 @@
background-image: url($imageUrl); background-image: url($imageUrl);
} }
} }
@mixin row-blocks {
display: flex;
min-height: 130px;
padding-bottom: 20px;
margin-bottom: 20px;
border-bottom: 1px solid #C6C6C6;
@media screen and (max-width: 800px) {
flex-direction: column;
height: auto;
text-align: center;
align-items: center;
}
}

View File

@ -12,6 +12,8 @@ $black-background: #000;
$grey-background: #f6f2f2; $grey-background: #f6f2f2;
$red-error: #FF0000; $red-error: #FF0000;
$grey-actor-name: #777272;
$expanded-horizontal-margins: 150px; $expanded-horizontal-margins: 150px;
$not-expanded-horizontal-margins: 30px; $not-expanded-horizontal-margins: 30px;

View File

@ -1,7 +1,6 @@
import * as Bull from 'bull' import * as Bull from 'bull'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { getServerActor } from '../../../helpers/utils' import { CONFIG, REMOTE_SCHEME, sequelizeTypescript } from '../../../initializers'
import { REMOTE_SCHEME, sequelizeTypescript } from '../../../initializers'
import { sendFollow } from '../../activitypub/send' import { sendFollow } from '../../activitypub/send'
import { sanitizeHost } from '../../../helpers/core-utils' import { sanitizeHost } from '../../../helpers/core-utils'
import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger'
@ -22,10 +21,14 @@ async function processActivityPubFollow (job: Bull.Job) {
logger.info('Processing ActivityPub follow in job %d.', job.id) logger.info('Processing ActivityPub follow in job %d.', job.id)
const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) let targetActor: ActorModel
if (!host || host === CONFIG.WEBSERVER.HOST) {
const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) targetActor = await ActorModel.loadLocalByName(payload.name)
const targetActor = await getOrCreateActorAndServerAndModel(actorUrl) } else {
const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP)
const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost)
targetActor = await getOrCreateActorAndServerAndModel(actorUrl)
}
const fromActor = await ActorModel.load(payload.followerActorId) const fromActor = await ActorModel.load(payload.followerActorId)

View File

@ -29,6 +29,7 @@ import { getSort } from '../utils'
import { ActorModel } from './actor' import { ActorModel } from './actor'
import { VideoChannelModel } from '../video/video-channel' import { VideoChannelModel } from '../video/video-channel'
import { IIncludeOptions } from '../../../node_modules/sequelize-typescript/lib/interfaces/IIncludeOptions' import { IIncludeOptions } from '../../../node_modules/sequelize-typescript/lib/interfaces/IIncludeOptions'
import { AccountModel } from '../account/account'
@Table({ @Table({
tableName: 'actorFollow', tableName: 'actorFollow',
@ -262,7 +263,13 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
include: [ include: [
{ {
model: VideoChannelModel, model: VideoChannelModel,
required: true required: true,
include: [
{
model: AccountModel,
required: true
}
]
} }
] ]
} }