aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2018-04-17 00:49:04 +0200
committerRigel <sendmemail@rigelk.eu>2018-04-17 01:09:06 +0200
commit244e76a552ef05a5067134b1065d26dd89246d8c (patch)
treea15fcd52bce99797fc9366572fea62a7a44aaabe /client/src/app/shared
parentc36d5a6b98056ef7fec3db43fbee880ee7332dcf (diff)
downloadPeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.tar.gz
PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.tar.zst
PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.zip
feature: initial syndication feeds support
Provides rss 2.0, atom 1.0 and json 1.0 feeds for videos (instance and account-wide) on listings and video-watch views. * still lacks redis caching * still lacks lastBuildDate support * still lacks channel-wide support * still lacks semantic annotation (for licenses, NSFW warnings, etc.) * still lacks love ( ˘ ³˘) * RSS: has MRSS support for torrent lists! * RSS: includes the first torrent in an enclosure * JSON: lists all torrents in the 'attachments' object * ATOM: lacking torrent listing support Advances #23 Partial implementation for the accountId generation in the client, which will need a hotfix to add a way to get the proper account id.
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/misc/object-length.pipe.ts8
-rw-r--r--client/src/app/shared/shared.module.ts9
-rw-r--r--client/src/app/shared/video/abstract-video-list.html2
-rw-r--r--client/src/app/shared/video/abstract-video-list.scss7
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts6
-rw-r--r--client/src/app/shared/video/video-feed.component.html14
-rw-r--r--client/src/app/shared/video/video-feed.component.scss19
-rw-r--r--client/src/app/shared/video/video-feed.component.ts14
-rw-r--r--client/src/app/shared/video/video.service.ts43
9 files changed, 121 insertions, 1 deletions
diff --git a/client/src/app/shared/misc/object-length.pipe.ts b/client/src/app/shared/misc/object-length.pipe.ts
new file mode 100644
index 000000000..84d182052
--- /dev/null
+++ b/client/src/app/shared/misc/object-length.pipe.ts
@@ -0,0 +1,8 @@
1import { Pipe, PipeTransform } from '@angular/core'
2
3@Pipe({ name: 'myObjectLength' })
4export class ObjectLengthPipe implements PipeTransform {
5 transform (value: Object) {
6 return Object.keys(value).length
7 }
8}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index eb50d45a9..74730e2aa 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -10,6 +10,7 @@ import { MarkdownService } from '@app/videos/shared'
10 10
11import { BsDropdownModule } from 'ngx-bootstrap/dropdown' 11import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
12import { ModalModule } from 'ngx-bootstrap/modal' 12import { ModalModule } from 'ngx-bootstrap/modal'
13import { PopoverModule } from 'ngx-bootstrap/popover'
13import { TabsModule } from 'ngx-bootstrap/tabs' 14import { TabsModule } from 'ngx-bootstrap/tabs'
14import { TooltipModule } from 'ngx-bootstrap/tooltip' 15import { TooltipModule } from 'ngx-bootstrap/tooltip'
15import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' 16import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
@@ -21,11 +22,13 @@ import { EditButtonComponent } from './misc/edit-button.component'
21import { FromNowPipe } from './misc/from-now.pipe' 22import { FromNowPipe } from './misc/from-now.pipe'
22import { LoaderComponent } from './misc/loader.component' 23import { LoaderComponent } from './misc/loader.component'
23import { NumberFormatterPipe } from './misc/number-formatter.pipe' 24import { NumberFormatterPipe } from './misc/number-formatter.pipe'
25import { ObjectLengthPipe } from './misc/object-length.pipe'
24import { RestExtractor, RestService } from './rest' 26import { RestExtractor, RestService } from './rest'
25import { UserService } from './users' 27import { UserService } from './users'
26import { VideoAbuseService } from './video-abuse' 28import { VideoAbuseService } from './video-abuse'
27import { VideoBlacklistService } from './video-blacklist' 29import { VideoBlacklistService } from './video-blacklist'
28import { VideoMiniatureComponent } from './video/video-miniature.component' 30import { VideoMiniatureComponent } from './video/video-miniature.component'
31import { VideoFeedComponent } from './video/video-feed.component'
29import { VideoThumbnailComponent } from './video/video-thumbnail.component' 32import { VideoThumbnailComponent } from './video/video-thumbnail.component'
30import { VideoService } from './video/video.service' 33import { VideoService } from './video/video.service'
31 34
@@ -39,6 +42,7 @@ import { VideoService } from './video/video.service'
39 42
40 BsDropdownModule.forRoot(), 43 BsDropdownModule.forRoot(),
41 ModalModule.forRoot(), 44 ModalModule.forRoot(),
45 PopoverModule.forRoot(),
42 TabsModule.forRoot(), 46 TabsModule.forRoot(),
43 TooltipModule.forRoot(), 47 TooltipModule.forRoot(),
44 48
@@ -50,9 +54,11 @@ import { VideoService } from './video/video.service'
50 LoaderComponent, 54 LoaderComponent,
51 VideoThumbnailComponent, 55 VideoThumbnailComponent,
52 VideoMiniatureComponent, 56 VideoMiniatureComponent,
57 VideoFeedComponent,
53 DeleteButtonComponent, 58 DeleteButtonComponent,
54 EditButtonComponent, 59 EditButtonComponent,
55 NumberFormatterPipe, 60 NumberFormatterPipe,
61 ObjectLengthPipe,
56 FromNowPipe, 62 FromNowPipe,
57 MarkdownTextareaComponent, 63 MarkdownTextareaComponent,
58 InfiniteScrollerDirective, 64 InfiniteScrollerDirective,
@@ -68,6 +74,7 @@ import { VideoService } from './video/video.service'
68 74
69 BsDropdownModule, 75 BsDropdownModule,
70 ModalModule, 76 ModalModule,
77 PopoverModule,
71 TabsModule, 78 TabsModule,
72 TooltipModule, 79 TooltipModule,
73 PrimeSharedModule, 80 PrimeSharedModule,
@@ -77,6 +84,7 @@ import { VideoService } from './video/video.service'
77 LoaderComponent, 84 LoaderComponent,
78 VideoThumbnailComponent, 85 VideoThumbnailComponent,
79 VideoMiniatureComponent, 86 VideoMiniatureComponent,
87 VideoFeedComponent,
80 DeleteButtonComponent, 88 DeleteButtonComponent,
81 EditButtonComponent, 89 EditButtonComponent,
82 MarkdownTextareaComponent, 90 MarkdownTextareaComponent,
@@ -84,6 +92,7 @@ import { VideoService } from './video/video.service'
84 HelpComponent, 92 HelpComponent,
85 93
86 NumberFormatterPipe, 94 NumberFormatterPipe,
95 ObjectLengthPipe,
87 FromNowPipe 96 FromNowPipe
88 ], 97 ],
89 98
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index 94a38019d..cb04e07b4 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -2,9 +2,9 @@
2 <div class="title-page title-page-single"> 2 <div class="title-page title-page-single">
3 {{ titlePage }} 3 {{ titlePage }}
4 </div> 4 </div>
5 <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed>
5 6
6 <div *ngIf="pagination.totalItems === 0">No results.</div> 7 <div *ngIf="pagination.totalItems === 0">No results.</div>
7
8 <div 8 <div
9 myInfiniteScroller 9 myInfiniteScroller
10 [pageHeight]="pageHeight" 10 [pageHeight]="pageHeight"
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/video/abstract-video-list.scss
index 63538a089..b75907dc9 100644
--- a/client/src/app/shared/video/abstract-video-list.scss
+++ b/client/src/app/shared/video/abstract-video-list.scss
@@ -1,3 +1,5 @@
1@import '_mixins';
2
1.videos { 3.videos {
2 text-align: center; 4 text-align: center;
3 5
@@ -6,6 +8,11 @@
6 } 8 }
7} 9}
8 10
11my-video-feed {
12 display: inline-block;
13 margin-left: -45px;
14}
15
9@media screen and (max-width: 500px) { 16@media screen and (max-width: 500px) {
10 .videos { 17 .videos {
11 text-align: center; 18 text-align: center;
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 1b9a519bd..024834dfc 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'
3import { isInMobileView } from '@app/shared/misc/utils' 3import { isInMobileView } from '@app/shared/misc/utils'
4import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' 4import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
5import { NotificationsService } from 'angular2-notifications' 5import { NotificationsService } from 'angular2-notifications'
6import { PopoverModule } from 'ngx-bootstrap/popover'
6import 'rxjs/add/operator/debounceTime' 7import 'rxjs/add/operator/debounceTime'
7import { Observable } from 'rxjs/Observable' 8import { Observable } from 'rxjs/Observable'
8import { fromEvent } from 'rxjs/observable/fromEvent' 9import { fromEvent } from 'rxjs/observable/fromEvent'
@@ -11,6 +12,8 @@ import { AuthService } from '../../core/auth'
11import { ComponentPagination } from '../rest/component-pagination.model' 12import { ComponentPagination } from '../rest/component-pagination.model'
12import { SortField } from './sort-field.type' 13import { SortField } from './sort-field.type'
13import { Video } from './video.model' 14import { Video } from './video.model'
15import { FeedFormat } from '../../../../../shared'
16import { VideoFeedComponent } from '@app/shared/video/video-feed.component'
14 17
15export abstract class AbstractVideoList implements OnInit, OnDestroy { 18export abstract class AbstractVideoList implements OnInit, OnDestroy {
16 private static LINES_PER_PAGE = 4 19 private static LINES_PER_PAGE = 4
@@ -25,6 +28,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
25 } 28 }
26 sort: SortField = '-createdAt' 29 sort: SortField = '-createdAt'
27 defaultSort: SortField = '-createdAt' 30 defaultSort: SortField = '-createdAt'
31 syndicationItems = {}
32
28 loadOnInit = true 33 loadOnInit = true
29 pageHeight: number 34 pageHeight: number
30 videoWidth: number 35 videoWidth: number
@@ -47,6 +52,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
47 private resizeSubscription: Subscription 52 private resizeSubscription: Subscription
48 53
49 abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number}> 54 abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number}>
55 abstract generateSyndicationList ()
50 56
51 get user () { 57 get user () {
52 return this.authService.getUser() 58 return this.authService.getUser()
diff --git a/client/src/app/shared/video/video-feed.component.html b/client/src/app/shared/video/video-feed.component.html
new file mode 100644
index 000000000..7733ef221
--- /dev/null
+++ b/client/src/app/shared/video/video-feed.component.html
@@ -0,0 +1,14 @@
1<div class="video-feed">
2 <span *ngIf="(syndicationItems | myObjectLength) >= 1" class="icon icon-syndication"
3 [popover]="feedsList"
4 placement="bottom"
5 [outsideClick]="true">
6 </span>
7
8 <ng-template #feedsList>
9 <div *ngFor="let key of syndicationItems | keys">
10 <a [href]="syndicationItems[key]">{{ key }}</a>
11 </div>
12 </ng-template>
13</div>
14 \ No newline at end of file
diff --git a/client/src/app/shared/video/video-feed.component.scss b/client/src/app/shared/video/video-feed.component.scss
new file mode 100644
index 000000000..2efeb405e
--- /dev/null
+++ b/client/src/app/shared/video/video-feed.component.scss
@@ -0,0 +1,19 @@
1@import '_mixins';
2
3.video-feed {
4 a {
5 @include disable-default-a-behaviour;
6
7 color: black;
8 }
9
10 .icon {
11 @include icon(12px);
12
13 &.icon-syndication {
14 position: relative;
15 top: -2px;
16 background-image: url('../../../assets/images/global/syndication.svg');
17 }
18 }
19} \ No newline at end of file
diff --git a/client/src/app/shared/video/video-feed.component.ts b/client/src/app/shared/video/video-feed.component.ts
new file mode 100644
index 000000000..41257ca99
--- /dev/null
+++ b/client/src/app/shared/video/video-feed.component.ts
@@ -0,0 +1,14 @@
1import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'
2
3@Component({
4 selector: 'my-video-feed',
5 styleUrls: [ './video-feed.component.scss' ],
6 templateUrl: './video-feed.component.html'
7})
8export class VideoFeedComponent implements OnChanges {
9 @Input() syndicationItems
10
11 ngOnChanges (changes: SimpleChanges) {
12 this.syndicationItems = changes.syndicationItems.currentValue
13 }
14}
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 0a8894fd9..009155410 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -8,6 +8,7 @@ import { ResultList } from '../../../../../shared/models/result-list.model'
8import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model' 8import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
9import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model' 9import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
10import { VideoFilter } from '../../../../../shared/models/videos/video-query.type' 10import { VideoFilter } from '../../../../../shared/models/videos/video-query.type'
11import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum'
11import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type' 12import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
12import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model' 13import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
13import { environment } from '../../../environments/environment' 14import { environment } from '../../../environments/environment'
@@ -24,6 +25,7 @@ import { objectToFormData } from '@app/shared/misc/utils'
24@Injectable() 25@Injectable()
25export class VideoService { 26export class VideoService {
26 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 27 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
28 private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
27 29
28 constructor ( 30 constructor (
29 private authHttp: HttpClient, 31 private authHttp: HttpClient,
@@ -115,6 +117,47 @@ export class VideoService {
115 .catch((res) => this.restExtractor.handleError(res)) 117 .catch((res) => this.restExtractor.handleError(res))
116 } 118 }
117 119
120 baseFeed () {
121 const feed = {}
122
123 for (let item in FeedFormat) {
124 feed[FeedFormat[item]] = VideoService.BASE_FEEDS_URL + item.toLowerCase()
125 }
126
127 return feed
128 }
129
130 getFeed (
131 filter?: VideoFilter
132 ) {
133 let params = this.restService.addRestGetParams(new HttpParams())
134 const feed = this.baseFeed()
135
136 if (filter) {
137 params = params.set('filter', filter)
138 }
139 for (let item in feed) {
140 feed[item] = feed[item] + ((params.toString().length === 0) ? '' : '?') + params.toString()
141 }
142
143 return feed
144 }
145
146 getAccountFeed (
147 accountId: number,
148 host?: string
149 ) {
150 let params = this.restService.addRestGetParams(new HttpParams())
151 const feed = this.baseFeed()
152
153 params = params.set('accountId', accountId.toString())
154 for (let item in feed) {
155 feed[item] = feed[item] + ((params.toString().length === 0) ? '' : '?') + params.toString()
156 }
157
158 return feed
159 }
160
118 searchVideos ( 161 searchVideos (
119 search: string, 162 search: string,
120 videoPagination: ComponentPagination, 163 videoPagination: ComponentPagination,