]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/shared/shared-custom-markup/custom-markup.service.ts
Add more filters for video miniatures
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-custom-markup / custom-markup.service.ts
1 import { ComponentRef, Injectable } from '@angular/core'
2 import { MarkdownService } from '@app/core'
3 import {
4 ButtonMarkupData,
5 ChannelMiniatureMarkupData,
6 ContainerMarkupData,
7 EmbedMarkupData,
8 PlaylistMiniatureMarkupData,
9 VideoFilter,
10 VideoMiniatureMarkupData,
11 VideosListMarkupData
12 } from '@shared/models'
13 import { DynamicElementService } from './dynamic-element.service'
14 import {
15 ButtonMarkupComponent,
16 ChannelMiniatureMarkupComponent,
17 EmbedMarkupComponent,
18 PlaylistMiniatureMarkupComponent,
19 VideoMiniatureMarkupComponent,
20 VideosListMarkupComponent
21 } from './peertube-custom-tags'
22
23 type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<any>
24 type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
25
26 @Injectable()
27 export class CustomMarkupService {
28 private angularBuilders: { [ selector: string ]: AngularBuilderFunction } = {
29 'peertube-button': el => this.buttonBuilder(el),
30 'peertube-video-embed': el => this.embedBuilder(el, 'video'),
31 'peertube-playlist-embed': el => this.embedBuilder(el, 'playlist'),
32 'peertube-video-miniature': el => this.videoMiniatureBuilder(el),
33 'peertube-playlist-miniature': el => this.playlistMiniatureBuilder(el),
34 'peertube-channel-miniature': el => this.channelMiniatureBuilder(el),
35 'peertube-videos-list': el => this.videosListBuilder(el)
36 }
37
38 private htmlBuilders: { [ selector: string ]: HTMLBuilderFunction } = {
39 'peertube-container': el => this.containerBuilder(el)
40 }
41
42 private customMarkdownRenderer: (text: string) => Promise<HTMLElement>
43
44 constructor (
45 private dynamicElementService: DynamicElementService,
46 private markdown: MarkdownService
47 ) {
48 this.customMarkdownRenderer = async (text: string) => this.buildElement(text)
49 }
50
51 getCustomMarkdownRenderer () {
52 return this.customMarkdownRenderer
53 }
54
55 async buildElement (text: string) {
56 const html = await this.markdown.customPageMarkdownToHTML(text, this.getSupportedTags())
57
58 const rootElement = document.createElement('div')
59 rootElement.innerHTML = html
60
61 for (const selector of Object.keys(this.htmlBuilders)) {
62 rootElement.querySelectorAll(selector)
63 .forEach((e: HTMLElement) => {
64 try {
65 const element = this.execHTMLBuilder(selector, e)
66 // Insert as first child
67 e.insertBefore(element, e.firstChild)
68 } catch (err) {
69 console.error('Cannot inject component %s.', selector, err)
70 }
71 })
72 }
73
74 for (const selector of Object.keys(this.angularBuilders)) {
75 rootElement.querySelectorAll(selector)
76 .forEach((e: HTMLElement) => {
77 try {
78 const component = this.execAngularBuilder(selector, e)
79
80 this.dynamicElementService.injectElement(e, component)
81 } catch (err) {
82 console.error('Cannot inject component %s.', selector, err)
83 }
84 })
85 }
86
87 return rootElement
88 }
89
90 private getSupportedTags () {
91 return Object.keys(this.angularBuilders)
92 .concat(Object.keys(this.htmlBuilders))
93 }
94
95 private execHTMLBuilder (selector: string, el: HTMLElement) {
96 return this.htmlBuilders[selector](el)
97 }
98
99 private execAngularBuilder (selector: string, el: HTMLElement) {
100 return this.angularBuilders[selector](el)
101 }
102
103 private embedBuilder (el: HTMLElement, type: 'video' | 'playlist') {
104 const data = el.dataset as EmbedMarkupData
105 const component = this.dynamicElementService.createElement(EmbedMarkupComponent)
106
107 this.dynamicElementService.setModel(component, { uuid: data.uuid, type })
108
109 return component
110 }
111
112 private playlistMiniatureBuilder (el: HTMLElement) {
113 const data = el.dataset as PlaylistMiniatureMarkupData
114 const component = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent)
115
116 this.dynamicElementService.setModel(component, { uuid: data.uuid })
117
118 return component
119 }
120
121 private channelMiniatureBuilder (el: HTMLElement) {
122 const data = el.dataset as ChannelMiniatureMarkupData
123 const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent)
124
125 this.dynamicElementService.setModel(component, { name: data.name })
126
127 return component
128 }
129
130 private buttonBuilder (el: HTMLElement) {
131 const data = el.dataset as ButtonMarkupData
132 const component = this.dynamicElementService.createElement(ButtonMarkupComponent)
133
134 const model = {
135 theme: data.theme,
136 href: data.href,
137 label: data.label,
138 blankTarget: this.buildBoolean(data.blankTarget)
139 }
140 this.dynamicElementService.setModel(component, model)
141
142 return component
143 }
144
145 private videoMiniatureBuilder (el: HTMLElement) {
146 const data = el.dataset as VideoMiniatureMarkupData
147 const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent)
148
149 const model = {
150 uuid: data.uuid,
151 onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false
152 }
153
154 this.dynamicElementService.setModel(component, model)
155
156 return component
157 }
158
159 private videosListBuilder (el: HTMLElement) {
160 const data = el.dataset as VideosListMarkupData
161 const component = this.dynamicElementService.createElement(VideosListMarkupComponent)
162
163 const model = {
164 onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false,
165 sort: data.sort || '-publishedAt',
166 categoryOneOf: this.buildArrayNumber(data.categoryOneOf) ?? [],
167 languageOneOf: this.buildArrayString(data.languageOneOf) ?? [],
168 filter: this.buildBoolean(data.onlyLocal) ? 'local' as VideoFilter : undefined,
169 count: this.buildNumber(data.count) || 10
170 }
171
172 this.dynamicElementService.setModel(component, model)
173
174 return component
175 }
176
177 private containerBuilder (el: HTMLElement) {
178 const data = el.dataset as ContainerMarkupData
179
180 const root = document.createElement('div')
181 root.classList.add('peertube-container')
182
183 if (data.width) {
184 root.setAttribute('width', data.width)
185 }
186
187 if (data.title) {
188 const titleElement = document.createElement('h4')
189 titleElement.innerText = data.title
190 root.appendChild(titleElement)
191 }
192
193 if (data.description) {
194 const descriptionElement = document.createElement('div')
195 descriptionElement.innerText = data.description
196 root.appendChild(descriptionElement)
197 }
198
199 return root
200 }
201
202 private buildNumber (value: string) {
203 if (!value) return undefined
204
205 return parseInt(value, 10)
206 }
207
208 private buildBoolean (value: string) {
209 if (value === 'true') return true
210 if (value === 'false') return false
211
212 return undefined
213 }
214
215 private buildArrayNumber (value: string) {
216 if (!value) return undefined
217
218 return value.split(',').map(v => parseInt(v, 10))
219 }
220
221 private buildArrayString (value: string) {
222 if (!value) return undefined
223
224 return value.split(',')
225 }
226 }