aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-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
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.html1
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.ts45
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.ts1
-rw-r--r--client/src/app/shared/shared-main/custom-page/custom-page.service.ts38
-rw-r--r--client/src/app/shared/shared-main/custom-page/index.ts1
-rw-r--r--client/src/app/shared/shared-main/shared-main.module.ts5
23 files changed, 579 insertions, 8 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}
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.html b/client/src/app/shared/shared-forms/markdown-textarea.component.html
index 513b543cd..6e70e2f37 100644
--- a/client/src/app/shared/shared-forms/markdown-textarea.component.html
+++ b/client/src/app/shared/shared-forms/markdown-textarea.component.html
@@ -19,6 +19,7 @@
19 <a ngbNavLink i18n>Complete preview</a> 19 <a ngbNavLink i18n>Complete preview</a>
20 20
21 <ng-template ngbNavContent> 21 <ng-template ngbNavContent>
22 <div #previewElement></div>
22 <div [innerHTML]="previewHTML"></div> 23 <div [innerHTML]="previewHTML"></div>
23 </ng-template> 24 </ng-template>
24 </ng-container> 25 </ng-container>
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.ts b/client/src/app/shared/shared-forms/markdown-textarea.component.ts
index 9b3ab9cf3..a233a4205 100644
--- a/client/src/app/shared/shared-forms/markdown-textarea.component.ts
+++ b/client/src/app/shared/shared-forms/markdown-textarea.component.ts
@@ -1,9 +1,10 @@
1import { ViewportScroller } from '@angular/common'
2import truncate from 'lodash-es/truncate' 1import truncate from 'lodash-es/truncate'
3import { Subject } from 'rxjs' 2import { Subject } from 'rxjs'
4import { debounceTime, distinctUntilChanged } from 'rxjs/operators' 3import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
4import { ViewportScroller } from '@angular/common'
5import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core' 5import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'
6import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 6import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
7import { SafeHtml } from '@angular/platform-browser'
7import { MarkdownService, ScreenService } from '@app/core' 8import { MarkdownService, ScreenService } from '@app/core'
8 9
9@Component({ 10@Component({
@@ -21,18 +22,27 @@ import { MarkdownService, ScreenService } from '@app/core'
21 22
22export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { 23export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
23 @Input() content = '' 24 @Input() content = ''
25
24 @Input() classes: string[] | { [klass: string]: any[] | any } = [] 26 @Input() classes: string[] | { [klass: string]: any[] | any } = []
27
25 @Input() textareaMaxWidth = '100%' 28 @Input() textareaMaxWidth = '100%'
26 @Input() textareaHeight = '150px' 29 @Input() textareaHeight = '150px'
30
27 @Input() truncate: number 31 @Input() truncate: number
32
28 @Input() markdownType: 'text' | 'enhanced' = 'text' 33 @Input() markdownType: 'text' | 'enhanced' = 'text'
34 @Input() customMarkdownRenderer?: (text: string) => Promise<string | HTMLElement>
35
29 @Input() markdownVideo = false 36 @Input() markdownVideo = false
37
30 @Input() name = 'description' 38 @Input() name = 'description'
31 39
32 @ViewChild('textarea') textareaElement: ElementRef 40 @ViewChild('textarea') textareaElement: ElementRef
41 @ViewChild('previewElement') previewElement: ElementRef
42
43 truncatedPreviewHTML: SafeHtml | string = ''
44 previewHTML: SafeHtml | string = ''
33 45
34 truncatedPreviewHTML = ''
35 previewHTML = ''
36 isMaximized = false 46 isMaximized = false
37 47
38 maximizeInText = $localize`Maximize editor` 48 maximizeInText = $localize`Maximize editor`
@@ -115,10 +125,31 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
115 } 125 }
116 126
117 private async markdownRender (text: string) { 127 private async markdownRender (text: string) {
118 const html = this.markdownType === 'text' ? 128 let html: string
119 await this.markdownService.textMarkdownToHTML(text) : 129
120 await this.markdownService.enhancedMarkdownToHTML(text) 130 if (this.customMarkdownRenderer) {
131 const result = await this.customMarkdownRenderer(text)
132
133 if (result instanceof HTMLElement) {
134 html = ''
135
136 const wrapperElement = this.previewElement.nativeElement as HTMLElement
137 wrapperElement.innerHTML = ''
138 wrapperElement.appendChild(result)
139 return
140 }
141
142 html = result
143 } else if (this.markdownType === 'text') {
144 html = await this.markdownService.textMarkdownToHTML(text)
145 } else {
146 html = await this.markdownService.enhancedMarkdownToHTML(text)
147 }
148
149 if (this.markdownVideo) {
150 html = this.markdownService.processVideoTimestamps(html)
151 }
121 152
122 return this.markdownVideo ? this.markdownService.processVideoTimestamps(html) : html 153 return html
123 } 154 }
124} 155}
diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts
index 3af517927..a4dd72db6 100644
--- a/client/src/app/shared/shared-icons/global-icon.component.ts
+++ b/client/src/app/shared/shared-icons/global-icon.component.ts
@@ -72,6 +72,7 @@ const icons = {
72 'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default, 72 'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default,
73 'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default, 73 'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default,
74 'codesandbox': require('!!raw-loader?!../../../assets/images/feather/codesandbox.svg').default, 74 'codesandbox': require('!!raw-loader?!../../../assets/images/feather/codesandbox.svg').default,
75 'octagon': require('!!raw-loader?!../../../assets/images/feather/octagon.svg').default,
75 'award': require('!!raw-loader?!../../../assets/images/feather/award.svg').default 76 'award': require('!!raw-loader?!../../../assets/images/feather/award.svg').default
76} 77}
77 78
diff --git a/client/src/app/shared/shared-main/custom-page/custom-page.service.ts b/client/src/app/shared/shared-main/custom-page/custom-page.service.ts
new file mode 100644
index 000000000..e5c2b3cd4
--- /dev/null
+++ b/client/src/app/shared/shared-main/custom-page/custom-page.service.ts
@@ -0,0 +1,38 @@
1import { of } from 'rxjs'
2import { catchError, map } from 'rxjs/operators'
3import { HttpClient } from '@angular/common/http'
4import { Injectable } from '@angular/core'
5import { RestExtractor } from '@app/core'
6import { CustomPage } from '@shared/models'
7import { environment } from '../../../../environments/environment'
8
9@Injectable()
10export class CustomPageService {
11 static BASE_INSTANCE_HOMEPAGE_URL = environment.apiUrl + '/api/v1/custom-pages/homepage/instance'
12
13 constructor (
14 private authHttp: HttpClient,
15 private restExtractor: RestExtractor
16 ) { }
17
18 getInstanceHomepage () {
19 return this.authHttp.get<CustomPage>(CustomPageService.BASE_INSTANCE_HOMEPAGE_URL)
20 .pipe(
21 catchError(err => {
22 if (err.status === 404) {
23 return of({ content: '' })
24 }
25
26 this.restExtractor.handleError(err)
27 })
28 )
29 }
30
31 updateInstanceHomepage (content: string) {
32 return this.authHttp.put(CustomPageService.BASE_INSTANCE_HOMEPAGE_URL, { content })
33 .pipe(
34 map(this.restExtractor.extractDataBool),
35 catchError(err => this.restExtractor.handleError(err))
36 )
37 }
38}
diff --git a/client/src/app/shared/shared-main/custom-page/index.ts b/client/src/app/shared/shared-main/custom-page/index.ts
new file mode 100644
index 000000000..7269ece95
--- /dev/null
+++ b/client/src/app/shared/shared-main/custom-page/index.ts
@@ -0,0 +1 @@
export * from './custom-page.service'
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts
index 772198cb2..f9b6085cf 100644
--- a/client/src/app/shared/shared-main/shared-main.module.ts
+++ b/client/src/app/shared/shared-main/shared-main.module.ts
@@ -29,6 +29,7 @@ import {
29} from './angular' 29} from './angular'
30import { AUTH_INTERCEPTOR_PROVIDER } from './auth' 30import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
31import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditButtonComponent } from './buttons' 31import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditButtonComponent } from './buttons'
32import { CustomPageService } from './custom-page'
32import { DateToggleComponent } from './date' 33import { DateToggleComponent } from './date'
33import { FeedComponent } from './feeds' 34import { FeedComponent } from './feeds'
34import { LoaderComponent, SmallLoaderComponent } from './loaders' 35import { LoaderComponent, SmallLoaderComponent } from './loaders'
@@ -171,7 +172,9 @@ import { VideoChannelService } from './video-channel'
171 172
172 VideoCaptionService, 173 VideoCaptionService,
173 174
174 VideoChannelService 175 VideoChannelService,
176
177 CustomPageService
175 ] 178 ]
176}) 179})
177export class SharedMainModule { } 180export class SharedMainModule { }