custom-markup.service.ts 6.75 KB
Newer Older
Chocobozzz's avatar
Chocobozzz committed
1
2
3
import { ComponentRef, Injectable } from '@angular/core'
import { MarkdownService } from '@app/core'
import {
4
  ButtonMarkupData,
Chocobozzz's avatar
Chocobozzz committed
5
  ChannelMiniatureMarkupData,
6
  ContainerMarkupData,
Chocobozzz's avatar
Chocobozzz committed
7
8
  EmbedMarkupData,
  PlaylistMiniatureMarkupData,
9
  VideoFilter,
Chocobozzz's avatar
Chocobozzz committed
10
11
12
13
  VideoMiniatureMarkupData,
  VideosListMarkupData
} from '@shared/models'
import { DynamicElementService } from './dynamic-element.service'
14
15
16
17
18
19
20
21
import {
  ButtonMarkupComponent,
  ChannelMiniatureMarkupComponent,
  EmbedMarkupComponent,
  PlaylistMiniatureMarkupComponent,
  VideoMiniatureMarkupComponent,
  VideosListMarkupComponent
} from './peertube-custom-tags'
Chocobozzz's avatar
Chocobozzz committed
22

23
24
type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<any>
type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
Chocobozzz's avatar
Chocobozzz committed
25
26
27

@Injectable()
export class CustomMarkupService {
28
  private angularBuilders: { [ selector: string ]: AngularBuilderFunction } = {
29
    'peertube-button': el => this.buttonBuilder(el),
Chocobozzz's avatar
Chocobozzz committed
30
31
32
33
34
35
36
37
    'peertube-video-embed': el => this.embedBuilder(el, 'video'),
    'peertube-playlist-embed': el => this.embedBuilder(el, 'playlist'),
    'peertube-video-miniature': el => this.videoMiniatureBuilder(el),
    'peertube-playlist-miniature': el => this.playlistMiniatureBuilder(el),
    'peertube-channel-miniature': el => this.channelMiniatureBuilder(el),
    'peertube-videos-list': el => this.videosListBuilder(el)
  }

38
39
40
41
  private htmlBuilders: { [ selector: string ]: HTMLBuilderFunction } = {
    'peertube-container': el => this.containerBuilder(el)
  }

42
43
  private customMarkdownRenderer: (text: string) => Promise<HTMLElement>

Chocobozzz's avatar
Chocobozzz committed
44
45
46
  constructor (
    private dynamicElementService: DynamicElementService,
    private markdown: MarkdownService
47
48
49
50
51
52
53
  ) {
    this.customMarkdownRenderer = async (text: string) => this.buildElement(text)
  }

  getCustomMarkdownRenderer () {
    return this.customMarkdownRenderer
  }
Chocobozzz's avatar
Chocobozzz committed
54
55
56
57
58
59
60

  async buildElement (text: string) {
    const html = await this.markdown.customPageMarkdownToHTML(text, this.getSupportedTags())

    const rootElement = document.createElement('div')
    rootElement.innerHTML = html

61
62
63
64
65
66
67
68
69
70
71
72
73
74
    for (const selector of Object.keys(this.htmlBuilders)) {
      rootElement.querySelectorAll(selector)
        .forEach((e: HTMLElement) => {
          try {
            const element = this.execHTMLBuilder(selector, e)
            // Insert as first child
            e.insertBefore(element, e.firstChild)
          } catch (err) {
            console.error('Cannot inject component %s.', selector, err)
          }
        })
    }

    for (const selector of Object.keys(this.angularBuilders)) {
Chocobozzz's avatar
Chocobozzz committed
75
76
77
      rootElement.querySelectorAll(selector)
        .forEach((e: HTMLElement) => {
          try {
78
            const component = this.execAngularBuilder(selector, e)
Chocobozzz's avatar
Chocobozzz committed
79
80
81
82
83
84
85
86
87
88
89
90

            this.dynamicElementService.injectElement(e, component)
          } catch (err) {
            console.error('Cannot inject component %s.', selector, err)
          }
        })
    }

    return rootElement
  }

  private getSupportedTags () {
91
92
    return Object.keys(this.angularBuilders)
      .concat(Object.keys(this.htmlBuilders))
Chocobozzz's avatar
Chocobozzz committed
93
94
  }

95
96
97
98
99
100
  private execHTMLBuilder (selector: string, el: HTMLElement) {
    return this.htmlBuilders[selector](el)
  }

  private execAngularBuilder (selector: string, el: HTMLElement) {
    return this.angularBuilders[selector](el)
Chocobozzz's avatar
Chocobozzz committed
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
  }

  private embedBuilder (el: HTMLElement, type: 'video' | 'playlist') {
    const data = el.dataset as EmbedMarkupData
    const component = this.dynamicElementService.createElement(EmbedMarkupComponent)

    this.dynamicElementService.setModel(component, { uuid: data.uuid, type })

    return component
  }

  private playlistMiniatureBuilder (el: HTMLElement) {
    const data = el.dataset as PlaylistMiniatureMarkupData
    const component = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent)

    this.dynamicElementService.setModel(component, { uuid: data.uuid })

    return component
  }

  private channelMiniatureBuilder (el: HTMLElement) {
    const data = el.dataset as ChannelMiniatureMarkupData
    const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent)

    this.dynamicElementService.setModel(component, { name: data.name })

    return component
  }

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
  private buttonBuilder (el: HTMLElement) {
    const data = el.dataset as ButtonMarkupData
    const component = this.dynamicElementService.createElement(ButtonMarkupComponent)

    const model = {
      theme: data.theme,
      href: data.href,
      label: data.label,
      blankTarget: this.buildBoolean(data.blankTarget)
    }
    this.dynamicElementService.setModel(component, model)

    return component
  }

145
146
147
148
149
150
151
152
153
154
155
156
157
158
  private videoMiniatureBuilder (el: HTMLElement) {
    const data = el.dataset as VideoMiniatureMarkupData
    const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent)

    const model = {
      uuid: data.uuid,
      onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false
    }

    this.dynamicElementService.setModel(component, model)

    return component
  }

Chocobozzz's avatar
Chocobozzz committed
159
160
161
162
163
  private videosListBuilder (el: HTMLElement) {
    const data = el.dataset as VideosListMarkupData
    const component = this.dynamicElementService.createElement(VideosListMarkupComponent)

    const model = {
164
165
166
167
168
      onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false,
      sort: data.sort || '-publishedAt',
      categoryOneOf: this.buildArrayNumber(data.categoryOneOf) ?? [],
      languageOneOf: this.buildArrayString(data.languageOneOf) ?? [],
      filter: this.buildBoolean(data.onlyLocal) ? 'local' as VideoFilter : undefined,
Chocobozzz's avatar
Chocobozzz committed
169
170
171
172
173
174
175
176
      count: this.buildNumber(data.count) || 10
    }

    this.dynamicElementService.setModel(component, model)

    return component
  }

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
  private containerBuilder (el: HTMLElement) {
    const data = el.dataset as ContainerMarkupData

    const root = document.createElement('div')
    root.classList.add('peertube-container')

    if (data.width) {
      root.setAttribute('width', data.width)
    }

    if (data.title) {
      const titleElement = document.createElement('h4')
      titleElement.innerText = data.title
      root.appendChild(titleElement)
    }

    if (data.description) {
      const descriptionElement = document.createElement('div')
      descriptionElement.innerText = data.description
      root.appendChild(descriptionElement)
    }

    return root
  }

Chocobozzz's avatar
Chocobozzz committed
202
203
204
205
206
207
  private buildNumber (value: string) {
    if (!value) return undefined

    return parseInt(value, 10)
  }

208
209
210
211
212
213
214
  private buildBoolean (value: string) {
    if (value === 'true') return true
    if (value === 'false') return false

    return undefined
  }

Chocobozzz's avatar
Chocobozzz committed
215
216
217
218
219
220
221
222
223
224
225
226
  private buildArrayNumber (value: string) {
    if (!value) return undefined

    return value.split(',').map(v => parseInt(v, 10))
  }

  private buildArrayString (value: string) {
    if (!value) return undefined

    return value.split(',')
  }
}