aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/shared-custom-markup
diff options
context:
space:
mode:
authorChocobozzz <chocobozzz@cpy.re>2021-05-27 15:59:55 +0200
committerGitHub <noreply@github.com>2021-05-27 15:59:55 +0200
commit2539932e16129992a2c0889b4ff527c265a8e2c7 (patch)
treefb5048e63e02a2485eb96d27455f43e4b22e8ae0 /client/src/app/shared/shared-custom-markup
parenteb34ec30e0b57286fc6f85160490d2e973a3b0b1 (diff)
downloadPeerTube-2539932e16129992a2c0889b4ff527c265a8e2c7.tar.gz
PeerTube-2539932e16129992a2c0889b4ff527c265a8e2c7.tar.zst
PeerTube-2539932e16129992a2c0889b4ff527c265a8e2c7.zip
Instance homepage support (#4007)
* Prepare homepage parsers * Add ability to update instance hompage * Add ability to set homepage as landing page * Add homepage preview in admin * Dynamically update left menu for homepage * Inject home content in homepage * Add videos list and channel miniature custom markup * Remove unused elements in markup service
Diffstat (limited to 'client/src/app/shared/shared-custom-markup')
-rw-r--r--client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html8
-rw-r--r--client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss9
-rw-r--r--client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts26
-rw-r--r--client/src/app/shared/shared-custom-markup/custom-markup.service.ts136
-rw-r--r--client/src/app/shared/shared-custom-markup/dynamic-element.service.ts57
-rw-r--r--client/src/app/shared/shared-custom-markup/embed-markup.component.ts22
-rw-r--r--client/src/app/shared/shared-custom-markup/index.ts3
-rw-r--r--client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html2
-rw-r--r--client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss7
-rw-r--r--client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts38
-rw-r--r--client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts49
-rw-r--r--client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html6
-rw-r--r--client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss7
-rw-r--r--client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts44
-rw-r--r--client/src/app/shared/shared-custom-markup/videos-list-markup.component.html13
-rw-r--r--client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss9
-rw-r--r--client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts60
17 files changed, 496 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html
new file mode 100644
index 000000000..da81006b9
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html
@@ -0,0 +1,8 @@
1<div *ngIf="channel" class="channel">
2 <my-actor-avatar [channel]="channel" size="34"></my-actor-avatar>
3
4 <div class="display-name">{{ channel.displayName }}</div>
5 <div class="username">{{ channel.name }}</div>
6
7 <div class="description">{{ channel.description }}</div>
8</div>
diff --git a/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss
new file mode 100644
index 000000000..85018afe2
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss
@@ -0,0 +1,9 @@
1@import '_variables';
2@import '_mixins';
3
4.channel {
5 border-radius: 15px;
6 padding: 10px;
7 width: min-content;
8 border: 1px solid pvar(--mainColor);
9}
diff --git a/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts
new file mode 100644
index 000000000..97bb5567e
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts
@@ -0,0 +1,26 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { VideoChannel, VideoChannelService } from '../shared-main'
3
4/*
5 * Markup component that creates a channel miniature only
6*/
7
8@Component({
9 selector: 'my-channel-miniature-markup',
10 templateUrl: 'channel-miniature-markup.component.html',
11 styleUrls: [ 'channel-miniature-markup.component.scss' ]
12})
13export class ChannelMiniatureMarkupComponent implements OnInit {
14 @Input() name: string
15
16 channel: VideoChannel
17
18 constructor (
19 private channelService: VideoChannelService
20 ) { }
21
22 ngOnInit () {
23 this.channelService.getVideoChannel(this.name)
24 .subscribe(channel => this.channel = channel)
25 }
26}
diff --git a/client/src/app/shared/shared-custom-markup/custom-markup.service.ts b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts
new file mode 100644
index 000000000..ffaf15710
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts
@@ -0,0 +1,136 @@
1import { ComponentRef, Injectable } from '@angular/core'
2import { MarkdownService } from '@app/core'
3import {
4 ChannelMiniatureMarkupData,
5 EmbedMarkupData,
6 PlaylistMiniatureMarkupData,
7 VideoMiniatureMarkupData,
8 VideosListMarkupData
9} from '@shared/models'
10import { ChannelMiniatureMarkupComponent } from './channel-miniature-markup.component'
11import { DynamicElementService } from './dynamic-element.service'
12import { EmbedMarkupComponent } from './embed-markup.component'
13import { PlaylistMiniatureMarkupComponent } from './playlist-miniature-markup.component'
14import { VideoMiniatureMarkupComponent } from './video-miniature-markup.component'
15import { VideosListMarkupComponent } from './videos-list-markup.component'
16
17type BuilderFunction = (el: HTMLElement) => ComponentRef<any>
18
19@Injectable()
20export class CustomMarkupService {
21 private builders: { [ selector: string ]: BuilderFunction } = {
22 'peertube-video-embed': el => this.embedBuilder(el, 'video'),
23 'peertube-playlist-embed': el => this.embedBuilder(el, 'playlist'),
24 'peertube-video-miniature': el => this.videoMiniatureBuilder(el),
25 'peertube-playlist-miniature': el => this.playlistMiniatureBuilder(el),
26 'peertube-channel-miniature': el => this.channelMiniatureBuilder(el),
27 'peertube-videos-list': el => this.videosListBuilder(el)
28 }
29
30 constructor (
31 private dynamicElementService: DynamicElementService,
32 private markdown: MarkdownService
33 ) { }
34
35 async buildElement (text: string) {
36 const html = await this.markdown.customPageMarkdownToHTML(text, this.getSupportedTags())
37
38 const rootElement = document.createElement('div')
39 rootElement.innerHTML = html
40
41 for (const selector of this.getSupportedTags()) {
42 rootElement.querySelectorAll(selector)
43 .forEach((e: HTMLElement) => {
44 try {
45 const component = this.execBuilder(selector, e)
46
47 this.dynamicElementService.injectElement(e, component)
48 } catch (err) {
49 console.error('Cannot inject component %s.', selector, err)
50 }
51 })
52 }
53
54 return rootElement
55 }
56
57 private getSupportedTags () {
58 return Object.keys(this.builders)
59 }
60
61 private execBuilder (selector: string, el: HTMLElement) {
62 return this.builders[selector](el)
63 }
64
65 private embedBuilder (el: HTMLElement, type: 'video' | 'playlist') {
66 const data = el.dataset as EmbedMarkupData
67 const component = this.dynamicElementService.createElement(EmbedMarkupComponent)
68
69 this.dynamicElementService.setModel(component, { uuid: data.uuid, type })
70
71 return component
72 }
73
74 private videoMiniatureBuilder (el: HTMLElement) {
75 const data = el.dataset as VideoMiniatureMarkupData
76 const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent)
77
78 this.dynamicElementService.setModel(component, { uuid: data.uuid })
79
80 return component
81 }
82
83 private playlistMiniatureBuilder (el: HTMLElement) {
84 const data = el.dataset as PlaylistMiniatureMarkupData
85 const component = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent)
86
87 this.dynamicElementService.setModel(component, { uuid: data.uuid })
88
89 return component
90 }
91
92 private channelMiniatureBuilder (el: HTMLElement) {
93 const data = el.dataset as ChannelMiniatureMarkupData
94 const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent)
95
96 this.dynamicElementService.setModel(component, { name: data.name })
97
98 return component
99 }
100
101 private videosListBuilder (el: HTMLElement) {
102 const data = el.dataset as VideosListMarkupData
103 const component = this.dynamicElementService.createElement(VideosListMarkupComponent)
104
105 const model = {
106 title: data.title,
107 description: data.description,
108 sort: data.sort,
109 categoryOneOf: this.buildArrayNumber(data.categoryOneOf),
110 languageOneOf: this.buildArrayString(data.languageOneOf),
111 count: this.buildNumber(data.count) || 10
112 }
113
114 this.dynamicElementService.setModel(component, model)
115
116 return component
117 }
118
119 private buildNumber (value: string) {
120 if (!value) return undefined
121
122 return parseInt(value, 10)
123 }
124
125 private buildArrayNumber (value: string) {
126 if (!value) return undefined
127
128 return value.split(',').map(v => parseInt(v, 10))
129 }
130
131 private buildArrayString (value: string) {
132 if (!value) return undefined
133
134 return value.split(',')
135 }
136}
diff --git a/client/src/app/shared/shared-custom-markup/dynamic-element.service.ts b/client/src/app/shared/shared-custom-markup/dynamic-element.service.ts
new file mode 100644
index 000000000..e967e30ac
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/dynamic-element.service.ts
@@ -0,0 +1,57 @@
1import {
2 ApplicationRef,
3 ComponentFactoryResolver,
4 ComponentRef,
5 EmbeddedViewRef,
6 Injectable,
7 Injector,
8 OnChanges,
9 SimpleChange,
10 SimpleChanges,
11 Type
12} from '@angular/core'
13
14@Injectable()
15export class DynamicElementService {
16
17 constructor (
18 private injector: Injector,
19 private applicationRef: ApplicationRef,
20 private componentFactoryResolver: ComponentFactoryResolver
21 ) { }
22
23 createElement <T> (ofComponent: Type<T>) {
24 const div = document.createElement('div')
25
26 const component = this.componentFactoryResolver.resolveComponentFactory(ofComponent)
27 .create(this.injector, [], div)
28
29 return component
30 }
31
32 injectElement <T> (wrapper: HTMLElement, componentRef: ComponentRef<T>) {
33 const hostView = componentRef.hostView as EmbeddedViewRef<any>
34
35 this.applicationRef.attachView(hostView)
36 wrapper.appendChild(hostView.rootNodes[0])
37 }
38
39 setModel <T> (componentRef: ComponentRef<T>, attributes: Partial<T>) {
40 const changes: SimpleChanges = {}
41
42 for (const key of Object.keys(attributes)) {
43 const previousValue = componentRef.instance[key]
44 const newValue = attributes[key]
45
46 componentRef.instance[key] = newValue
47 changes[key] = new SimpleChange(previousValue, newValue, previousValue === undefined)
48 }
49
50 const component = componentRef.instance
51 if (typeof (component as unknown as OnChanges).ngOnChanges === 'function') {
52 (component as unknown as OnChanges).ngOnChanges(changes)
53 }
54
55 componentRef.changeDetectorRef.detectChanges()
56 }
57}
diff --git a/client/src/app/shared/shared-custom-markup/embed-markup.component.ts b/client/src/app/shared/shared-custom-markup/embed-markup.component.ts
new file mode 100644
index 000000000..a854d89f6
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/embed-markup.component.ts
@@ -0,0 +1,22 @@
1import { buildPlaylistLink, buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
2import { environment } from 'src/environments/environment'
3import { Component, ElementRef, Input, OnInit } from '@angular/core'
4
5@Component({
6 selector: 'my-embed-markup',
7 template: ''
8})
9export class EmbedMarkupComponent implements OnInit {
10 @Input() uuid: string
11 @Input() type: 'video' | 'playlist' = 'video'
12
13 constructor (private el: ElementRef) { }
14
15 ngOnInit () {
16 const link = this.type === 'video'
17 ? buildVideoLink({ baseUrl: `${environment.originServerUrl}/videos/embed/${this.uuid}` })
18 : buildPlaylistLink({ baseUrl: `${environment.originServerUrl}/video-playlists/embed/${this.uuid}` })
19
20 this.el.nativeElement.innerHTML = buildVideoOrPlaylistEmbed(link, this.uuid)
21 }
22}
diff --git a/client/src/app/shared/shared-custom-markup/index.ts b/client/src/app/shared/shared-custom-markup/index.ts
new file mode 100644
index 000000000..14bde3ea9
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/index.ts
@@ -0,0 +1,3 @@
1export * from './custom-markup.service'
2export * from './dynamic-element.service'
3export * from './shared-custom-markup.module'
diff --git a/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html
new file mode 100644
index 000000000..4e1d1a13f
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html
@@ -0,0 +1,2 @@
1<my-video-playlist-miniature *ngIf="playlist" [playlist]="playlist">
2</my-video-playlist-miniature>
diff --git a/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss
new file mode 100644
index 000000000..281cef726
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss
@@ -0,0 +1,7 @@
1@import '_variables';
2@import '_mixins';
3
4my-video-playlist-miniature {
5 display: inline-block;
6 width: $video-thumbnail-width;
7}
diff --git a/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts
new file mode 100644
index 000000000..7aee450f1
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts
@@ -0,0 +1,38 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { MiniatureDisplayOptions } from '../shared-video-miniature'
3import { VideoPlaylist, VideoPlaylistService } from '../shared-video-playlist'
4
5/*
6 * Markup component that creates a playlist miniature only
7*/
8
9@Component({
10 selector: 'my-playlist-miniature-markup',
11 templateUrl: 'playlist-miniature-markup.component.html',
12 styleUrls: [ 'playlist-miniature-markup.component.scss' ]
13})
14export class PlaylistMiniatureMarkupComponent implements OnInit {
15 @Input() uuid: string
16
17 playlist: VideoPlaylist
18
19 displayOptions: MiniatureDisplayOptions = {
20 date: true,
21 views: true,
22 by: true,
23 avatar: false,
24 privacyLabel: false,
25 privacyText: false,
26 state: false,
27 blacklistInfo: false
28 }
29
30 constructor (
31 private playlistService: VideoPlaylistService
32 ) { }
33
34 ngOnInit () {
35 this.playlistService.getVideoPlaylist(this.uuid)
36 .subscribe(playlist => this.playlist = playlist)
37 }
38}
diff --git a/client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts b/client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts
new file mode 100644
index 000000000..4bbb71588
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts
@@ -0,0 +1,49 @@
1
2import { CommonModule } from '@angular/common'
3import { NgModule } from '@angular/core'
4import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
5import { SharedGlobalIconModule } from '../shared-icons'
6import { SharedMainModule } from '../shared-main'
7import { SharedVideoMiniatureModule } from '../shared-video-miniature'
8import { SharedVideoPlaylistModule } from '../shared-video-playlist'
9import { ChannelMiniatureMarkupComponent } from './channel-miniature-markup.component'
10import { CustomMarkupService } from './custom-markup.service'
11import { DynamicElementService } from './dynamic-element.service'
12import { EmbedMarkupComponent } from './embed-markup.component'
13import { PlaylistMiniatureMarkupComponent } from './playlist-miniature-markup.component'
14import { VideoMiniatureMarkupComponent } from './video-miniature-markup.component'
15import { VideosListMarkupComponent } from './videos-list-markup.component'
16
17@NgModule({
18 imports: [
19 CommonModule,
20
21 SharedMainModule,
22 SharedGlobalIconModule,
23 SharedVideoMiniatureModule,
24 SharedVideoPlaylistModule,
25 SharedActorImageModule
26 ],
27
28 declarations: [
29 VideoMiniatureMarkupComponent,
30 PlaylistMiniatureMarkupComponent,
31 ChannelMiniatureMarkupComponent,
32 EmbedMarkupComponent,
33 VideosListMarkupComponent
34 ],
35
36 exports: [
37 VideoMiniatureMarkupComponent,
38 PlaylistMiniatureMarkupComponent,
39 ChannelMiniatureMarkupComponent,
40 VideosListMarkupComponent,
41 EmbedMarkupComponent
42 ],
43
44 providers: [
45 CustomMarkupService,
46 DynamicElementService
47 ]
48})
49export class SharedCustomMarkupModule { }
diff --git a/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html
new file mode 100644
index 000000000..9b4930b6d
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html
@@ -0,0 +1,6 @@
1<my-video-miniature
2 *ngIf="video"
3 [video]="video" [user]="getUser()" [displayAsRow]="false"
4 [displayVideoActions]="false" [displayOptions]="displayOptions"
5>
6</my-video-miniature>
diff --git a/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss
new file mode 100644
index 000000000..81e265f29
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss
@@ -0,0 +1,7 @@
1@import '_variables';
2@import '_mixins';
3
4my-video-miniature {
5 display: inline-block;
6 width: $video-thumbnail-width;
7}
diff --git a/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts
new file mode 100644
index 000000000..79add0c3b
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts
@@ -0,0 +1,44 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { AuthService } from '@app/core'
3import { Video, VideoService } from '../shared-main'
4import { MiniatureDisplayOptions } from '../shared-video-miniature'
5
6/*
7 * Markup component that creates a video miniature only
8*/
9
10@Component({
11 selector: 'my-video-miniature-markup',
12 templateUrl: 'video-miniature-markup.component.html',
13 styleUrls: [ 'video-miniature-markup.component.scss' ]
14})
15export class VideoMiniatureMarkupComponent implements OnInit {
16 @Input() uuid: string
17
18 video: Video
19
20 displayOptions: MiniatureDisplayOptions = {
21 date: true,
22 views: true,
23 by: true,
24 avatar: false,
25 privacyLabel: false,
26 privacyText: false,
27 state: false,
28 blacklistInfo: false
29 }
30
31 constructor (
32 private auth: AuthService,
33 private videoService: VideoService
34 ) { }
35
36 getUser () {
37 return this.auth.getUser()
38 }
39
40 ngOnInit () {
41 this.videoService.getVideo({ videoId: this.uuid })
42 .subscribe(video => this.video = video)
43 }
44}
diff --git a/client/src/app/shared/shared-custom-markup/videos-list-markup.component.html b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.html
new file mode 100644
index 000000000..501f35e04
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.html
@@ -0,0 +1,13 @@
1<div class="root">
2 <h4 *ngIf="title">{{ title }}</h4>
3 <div *ngIf="description" class="description">{{ description }}</div>
4
5 <div class="videos">
6 <my-video-miniature
7 *ngFor="let video of videos"
8 [video]="video" [user]="getUser()" [displayAsRow]="false"
9 [displayVideoActions]="false" [displayOptions]="displayOptions"
10 >
11 </my-video-miniature>
12 </div>
13</div>
diff --git a/client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss
new file mode 100644
index 000000000..dcd931090
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss
@@ -0,0 +1,9 @@
1@import '_variables';
2@import '_mixins';
3
4my-video-miniature {
5 margin-right: 15px;
6 display: inline-block;
7 min-width: $video-thumbnail-width;
8 max-width: $video-thumbnail-width;
9}
diff --git a/client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts
new file mode 100644
index 000000000..cc25d0a51
--- /dev/null
+++ b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts
@@ -0,0 +1,60 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { AuthService } from '@app/core'
3import { VideoSortField } from '@shared/models'
4import { Video, VideoService } from '../shared-main'
5import { MiniatureDisplayOptions } from '../shared-video-miniature'
6
7/*
8 * Markup component list videos depending on criterias
9*/
10
11@Component({
12 selector: 'my-videos-list-markup',
13 templateUrl: 'videos-list-markup.component.html',
14 styleUrls: [ 'videos-list-markup.component.scss' ]
15})
16export class VideosListMarkupComponent implements OnInit {
17 @Input() title: string
18 @Input() description: string
19 @Input() sort = '-publishedAt'
20 @Input() categoryOneOf: number[]
21 @Input() languageOneOf: string[]
22 @Input() count = 10
23
24 videos: Video[]
25
26 displayOptions: MiniatureDisplayOptions = {
27 date: true,
28 views: true,
29 by: true,
30 avatar: false,
31 privacyLabel: false,
32 privacyText: false,
33 state: false,
34 blacklistInfo: false
35 }
36
37 constructor (
38 private auth: AuthService,
39 private videoService: VideoService
40 ) { }
41
42 getUser () {
43 return this.auth.getUser()
44 }
45
46 ngOnInit () {
47 const options = {
48 videoPagination: {
49 currentPage: 1,
50 itemsPerPage: this.count
51 },
52 categoryOneOf: this.categoryOneOf,
53 languageOneOf: this.languageOneOf,
54 sort: this.sort as VideoSortField
55 }
56
57 this.videoService.getVideos(options)
58 .subscribe(({ data }) => this.videos = data)
59 }
60}