X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2Fshared%2Fshared-custom-markup%2Fcustom-markup.service.ts;h=15da94709eccf7ae59a637ebb965e2accdca956f;hb=0ca454e3bdf89390d1a48760ab555ddd8725c82d;hp=ffaf1571015fd997f218892cc18ecc4071ea29eb;hpb=1c5e49e75284100b7b1fc8b4e73c8ba53fe22e89;p=github%2FChocobozzz%2FPeerTube.git 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 index ffaf15710..15da94709 100644 --- a/client/src/app/shared/shared-custom-markup/custom-markup.service.ts +++ b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts @@ -1,24 +1,34 @@ +import { first } from 'rxjs/operators' import { ComponentRef, Injectable } from '@angular/core' import { MarkdownService } from '@app/core' import { + ButtonMarkupData, ChannelMiniatureMarkupData, + ContainerMarkupData, EmbedMarkupData, PlaylistMiniatureMarkupData, + VideoFilter, VideoMiniatureMarkupData, VideosListMarkupData } from '@shared/models' -import { ChannelMiniatureMarkupComponent } from './channel-miniature-markup.component' import { DynamicElementService } from './dynamic-element.service' -import { EmbedMarkupComponent } from './embed-markup.component' -import { PlaylistMiniatureMarkupComponent } from './playlist-miniature-markup.component' -import { VideoMiniatureMarkupComponent } from './video-miniature-markup.component' -import { VideosListMarkupComponent } from './videos-list-markup.component' - -type BuilderFunction = (el: HTMLElement) => ComponentRef +import { + ButtonMarkupComponent, + ChannelMiniatureMarkupComponent, + EmbedMarkupComponent, + PlaylistMiniatureMarkupComponent, + VideoMiniatureMarkupComponent, + VideosListMarkupComponent +} from './peertube-custom-tags' +import { CustomMarkupComponent } from './peertube-custom-tags/shared' + +type AngularBuilderFunction = (el: HTMLElement) => ComponentRef +type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement @Injectable() export class CustomMarkupService { - private builders: { [ selector: string ]: BuilderFunction } = { + private angularBuilders: { [ selector: string ]: AngularBuilderFunction } = { + 'peertube-button': el => this.buttonBuilder(el), 'peertube-video-embed': el => this.embedBuilder(el, 'video'), 'peertube-playlist-embed': el => this.embedBuilder(el, 'playlist'), 'peertube-video-miniature': el => this.videoMiniatureBuilder(el), @@ -27,10 +37,25 @@ export class CustomMarkupService { 'peertube-videos-list': el => this.videosListBuilder(el) } + private htmlBuilders: { [ selector: string ]: HTMLBuilderFunction } = { + 'peertube-container': el => this.containerBuilder(el) + } + + private customMarkdownRenderer: (text: string) => Promise + constructor ( private dynamicElementService: DynamicElementService, private markdown: MarkdownService - ) { } + ) { + this.customMarkdownRenderer = (text: string) => { + return this.buildElement(text) + .then(({ rootElement }) => rootElement) + } + } + + getCustomMarkdownRenderer () { + return this.customMarkdownRenderer + } async buildElement (text: string) { const html = await this.markdown.customPageMarkdownToHTML(text, this.getSupportedTags()) @@ -38,11 +63,31 @@ export class CustomMarkupService { const rootElement = document.createElement('div') rootElement.innerHTML = html - for (const selector of this.getSupportedTags()) { + 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) + } + }) + } + + const loadedPromises: Promise[] = [] + + for (const selector of Object.keys(this.angularBuilders)) { rootElement.querySelectorAll(selector) .forEach((e: HTMLElement) => { try { - const component = this.execBuilder(selector, e) + const component = this.execAngularBuilder(selector, e) + + if (component.instance.loaded) { + const p = component.instance.loaded.pipe(first()).toPromise() + loadedPromises.push(p) + } this.dynamicElementService.injectElement(e, component) } catch (err) { @@ -51,15 +96,20 @@ export class CustomMarkupService { }) } - return rootElement + return { rootElement, componentsLoaded: Promise.all(loadedPromises) } } private getSupportedTags () { - return Object.keys(this.builders) + return Object.keys(this.angularBuilders) + .concat(Object.keys(this.htmlBuilders)) } - private execBuilder (selector: string, el: HTMLElement) { - return this.builders[selector](el) + private execHTMLBuilder (selector: string, el: HTMLElement) { + return this.htmlBuilders[selector](el) + } + + private execAngularBuilder (selector: string, el: HTMLElement) { + return this.angularBuilders[selector](el) } private embedBuilder (el: HTMLElement, type: 'video' | 'playlist') { @@ -71,15 +121,6 @@ export class CustomMarkupService { return component } - private videoMiniatureBuilder (el: HTMLElement) { - const data = el.dataset as VideoMiniatureMarkupData - const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent) - - this.dynamicElementService.setModel(component, { uuid: data.uuid }) - - return component - } - private playlistMiniatureBuilder (el: HTMLElement) { const data = el.dataset as PlaylistMiniatureMarkupData const component = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent) @@ -93,7 +134,42 @@ export class CustomMarkupService { const data = el.dataset as ChannelMiniatureMarkupData const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent) - this.dynamicElementService.setModel(component, { name: data.name }) + const model = { + name: data.name, + displayLatestVideo: this.buildBoolean(data.displayLatestVideo) ?? true, + displayDescription: this.buildBoolean(data.displayDescription) ?? true + } + + this.dynamicElementService.setModel(component, model) + + return component + } + + private buttonBuilder (el: HTMLElement) { + const data = el.dataset as ButtonMarkupData + const component = this.dynamicElementService.createElement(ButtonMarkupComponent) + + const model = { + theme: data.theme ?? 'primary', + href: data.href, + label: data.label, + blankTarget: this.buildBoolean(data.blankTarget) ?? false + } + this.dynamicElementService.setModel(component, model) + + return component + } + + 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 } @@ -103,12 +179,16 @@ export class CustomMarkupService { const component = this.dynamicElementService.createElement(VideosListMarkupComponent) const model = { - title: data.title, - description: data.description, - sort: data.sort, - categoryOneOf: this.buildArrayNumber(data.categoryOneOf), - languageOneOf: this.buildArrayString(data.languageOneOf), - count: this.buildNumber(data.count) || 10 + onlyDisplayTitle: this.buildBoolean(data.onlyDisplayTitle) ?? false, + maxRows: this.buildNumber(data.maxRows) ?? -1, + + sort: data.sort || '-publishedAt', + count: this.buildNumber(data.count) || 10, + + categoryOneOf: this.buildArrayNumber(data.categoryOneOf) ?? [], + languageOneOf: this.buildArrayString(data.languageOneOf) ?? [], + + filter: this.buildBoolean(data.onlyLocal) ? 'local' as VideoFilter : undefined } this.dynamicElementService.setModel(component, model) @@ -116,12 +196,61 @@ export class CustomMarkupService { return component } + private containerBuilder (el: HTMLElement) { + const data = el.dataset as ContainerMarkupData + + // Move inner HTML in the new element we'll create + const content = el.innerHTML + el.innerHTML = '' + + const root = document.createElement('div') + root.innerHTML = content + + const layoutClass = data.layout + ? 'layout-' + data.layout + : 'layout-column' + + root.classList.add('peertube-container', layoutClass) + + if (data.width) { + root.setAttribute('width', data.width) + } + + if (data.title || data.description) { + const headerElement = document.createElement('div') + headerElement.classList.add('header') + + if (data.title) { + const titleElement = document.createElement('h4') + titleElement.innerText = data.title + headerElement.appendChild(titleElement) + } + + if (data.description) { + const descriptionElement = document.createElement('div') + descriptionElement.innerText = data.description + headerElement.append(descriptionElement) + } + + root.insertBefore(headerElement, root.firstChild) + } + + return root + } + private buildNumber (value: string) { if (!value) return undefined return parseInt(value, 10) } + private buildBoolean (value: string) { + if (value === 'true') return true + if (value === 'false') return false + + return undefined + } + private buildArrayNumber (value: string) { if (!value) return undefined