]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/shared/shared-custom-markup/custom-markup.service.ts
Fix peertube container in markdown preview
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-custom-markup / custom-markup.service.ts
CommitLineData
2539932e
C
1import { ComponentRef, Injectable } from '@angular/core'
2import { MarkdownService } from '@app/core'
3import {
63042139 4 ButtonMarkupData,
2539932e 5 ChannelMiniatureMarkupData,
f7894f09 6 ContainerMarkupData,
2539932e
C
7 EmbedMarkupData,
8 PlaylistMiniatureMarkupData,
9105634f 9 VideoFilter,
2539932e
C
10 VideoMiniatureMarkupData,
11 VideosListMarkupData
12} from '@shared/models'
2539932e 13import { DynamicElementService } from './dynamic-element.service'
8ee25e17
C
14import {
15 ButtonMarkupComponent,
16 ChannelMiniatureMarkupComponent,
17 EmbedMarkupComponent,
18 PlaylistMiniatureMarkupComponent,
19 VideoMiniatureMarkupComponent,
20 VideosListMarkupComponent
21} from './peertube-custom-tags'
2539932e 22
f7894f09
C
23type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<any>
24type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
2539932e
C
25
26@Injectable()
27export class CustomMarkupService {
f7894f09 28 private angularBuilders: { [ selector: string ]: AngularBuilderFunction } = {
63042139 29 'peertube-button': el => this.buttonBuilder(el),
2539932e
C
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
f7894f09
C
38 private htmlBuilders: { [ selector: string ]: HTMLBuilderFunction } = {
39 'peertube-container': el => this.containerBuilder(el)
40 }
41
8ee25e17
C
42 private customMarkdownRenderer: (text: string) => Promise<HTMLElement>
43
2539932e
C
44 constructor (
45 private dynamicElementService: DynamicElementService,
46 private markdown: MarkdownService
8ee25e17
C
47 ) {
48 this.customMarkdownRenderer = async (text: string) => this.buildElement(text)
49 }
50
51 getCustomMarkdownRenderer () {
52 return this.customMarkdownRenderer
53 }
2539932e
C
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
f7894f09
C
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)) {
2539932e
C
75 rootElement.querySelectorAll(selector)
76 .forEach((e: HTMLElement) => {
77 try {
f7894f09 78 const component = this.execAngularBuilder(selector, e)
2539932e
C
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 () {
f7894f09
C
91 return Object.keys(this.angularBuilders)
92 .concat(Object.keys(this.htmlBuilders))
2539932e
C
93 }
94
f7894f09
C
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)
2539932e
C
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
2539932e
C
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
61cbafc1
C
125 const model = {
126 name: data.name,
127 displayLatestVideo: this.buildBoolean(data.displayLatestVideo) ?? true,
128 displayDescription: this.buildBoolean(data.displayDescription) ?? true
129 }
130
131 this.dynamicElementService.setModel(component, model)
2539932e
C
132
133 return component
134 }
135
63042139
C
136 private buttonBuilder (el: HTMLElement) {
137 const data = el.dataset as ButtonMarkupData
138 const component = this.dynamicElementService.createElement(ButtonMarkupComponent)
139
140 const model = {
4ead40e7 141 theme: data.theme ?? 'primary',
63042139
C
142 href: data.href,
143 label: data.label,
4ead40e7 144 blankTarget: this.buildBoolean(data.blankTarget) ?? false
63042139
C
145 }
146 this.dynamicElementService.setModel(component, model)
147
148 return component
149 }
150
9105634f
C
151 private videoMiniatureBuilder (el: HTMLElement) {
152 const data = el.dataset as VideoMiniatureMarkupData
153 const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent)
154
155 const model = {
156 uuid: data.uuid,
157 onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false
158 }
159
160 this.dynamicElementService.setModel(component, model)
161
162 return component
163 }
164
2539932e
C
165 private videosListBuilder (el: HTMLElement) {
166 const data = el.dataset as VideosListMarkupData
167 const component = this.dynamicElementService.createElement(VideosListMarkupComponent)
168
169 const model = {
9105634f 170 onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false,
5d6395af
C
171 maxRows: this.buildNumber(data.maxRows) ?? -1,
172
9105634f 173 sort: data.sort || '-publishedAt',
5d6395af
C
174 count: this.buildNumber(data.count) || 10,
175
9105634f
C
176 categoryOneOf: this.buildArrayNumber(data.categoryOneOf) ?? [],
177 languageOneOf: this.buildArrayString(data.languageOneOf) ?? [],
5d6395af
C
178
179 filter: this.buildBoolean(data.onlyLocal) ? 'local' as VideoFilter : undefined
2539932e
C
180 }
181
182 this.dynamicElementService.setModel(component, model)
183
184 return component
185 }
186
f7894f09
C
187 private containerBuilder (el: HTMLElement) {
188 const data = el.dataset as ContainerMarkupData
189
5d6395af
C
190 // Move inner HTML in the new element we'll create
191 const content = el.innerHTML
192 el.innerHTML = ''
193
f7894f09 194 const root = document.createElement('div')
5d6395af 195 root.innerHTML = content
61cbafc1
C
196
197 const layoutClass = data.layout
198 ? 'layout-' + data.layout
5d6395af 199 : 'layout-column'
61cbafc1
C
200
201 root.classList.add('peertube-container', layoutClass)
f7894f09
C
202
203 if (data.width) {
204 root.setAttribute('width', data.width)
205 }
206
5d6395af
C
207 if (data.title || data.description) {
208 const headerElement = document.createElement('div')
209 headerElement.classList.add('header')
210
211 if (data.title) {
212 const titleElement = document.createElement('h4')
213 titleElement.innerText = data.title
214 headerElement.appendChild(titleElement)
215 }
216
217 if (data.description) {
218 const descriptionElement = document.createElement('div')
219 descriptionElement.innerText = data.description
220 headerElement.append(descriptionElement)
221 }
f7894f09 222
5d6395af 223 root.insertBefore(headerElement, root.firstChild)
f7894f09
C
224 }
225
226 return root
227 }
228
2539932e
C
229 private buildNumber (value: string) {
230 if (!value) return undefined
231
232 return parseInt(value, 10)
233 }
234
63042139
C
235 private buildBoolean (value: string) {
236 if (value === 'true') return true
237 if (value === 'false') return false
238
239 return undefined
240 }
241
2539932e
C
242 private buildArrayNumber (value: string) {
243 if (!value) return undefined
244
245 return value.split(',').map(v => parseInt(v, 10))
246 }
247
248 private buildArrayString (value: string) {
249 if (!value) return undefined
250
251 return value.split(',')
252 }
253}